In this example, we are using a CITEseq dataset of mouse glioblastoma model.This dataset contains CD45+ sorted cells from a GL261 glioblastoma tumor of wild type and CCR2 knockout mice (WT4 and KO4).You can download the expression matrix here.

suppressPackageStartupMessages({
  library(Seurat)
  library(scater)
  library(dplyr)
  library(VennDiagram)
  library(mvoutlier)
  library(harmony)
  library(clustree)
  library(cowplot)
  library(scDblFinder)
  library(ggplot2)
  library(ggrepel)
})

Set the working directory

knitr::opts_knit$set(root.dir = "path/to/directory")

1) LOADING THE INPUT DATA

We use the Read10X function to read in the expresion matrix from the Cell Ranger output, which returns a list of two matrices: “Gene Expression” and “Antibody Capture”. We load the “Gene Expression” matrix into a Seurat object.

seur<-CreateSeuratObject(counts = Read10X(paste0("data/Citeseq_mouse_GBM/filtered_feature_bc_matrix"))[["Gene Expression"]] )
10X data contains more than one type and is being returned as a list containing matrices of each type.
### For single-cell RNA sequencing experiment wihout feature barcoding, use:
### seur<-CreateSeuratObject(counts = Read10X(paste0("path/to/filtered_feature_bc_matrix")) )

Identify the different samples based on the cell barcode names

head(colnames(seur))
[1] "AAACCCACAAGCGCTC-1" "AAACCCACAAGTCCCG-1" "AAACCCACACTGGCCA-1" "AAACCCACAGAGGACT-1" "AAACCCACAGCTGGTC-1" "AAACCCACAGTAGAAT-1"
seur$sample<-sapply(strsplit(colnames(seur), "-"), "[[",2)
table(seur$sample)

    1     2 
15850 12434 
seur$sample<-plyr::mapvalues(seur$sample, from=1:2, to=c("KO1","WT1"))

Alternatively, if starting from the individual expression matrices for each sample,we can load them as a list of seurat objects and then merge them using the Seurat function “merge”:

# sample_folder_names=c("KO1","WT1")
### sample_folder_names - a vector with the names of the folders that contain the Cellranger outputs for each sample
# seur=list()
# for ( i in c(sample_folder_names)) {  
#   seur[[i]]<-CreateSeuratObject(counts = Read10X(paste0(i,"/filtered_feature_bc_matrix"))[["Gene Expression"]] )
#   seur[[i]]$sample=i
# }
# seur<-merge(seur[[1]],seur[2:length(seur)])

2) QC: CELLS

2.1) Find outliers for total UMI counts, number of genes and percent mitohondrial genes per cell.

Outliers are determined based on the median absolute deviation (MAD). The goal is to remove the long tail before/after the peak in the sitribution of the QC metric. It is possible to modify the nmads parameter (minimum number of MADs away from median, required for a value to be called an outlier), or to set the threshold manually (e.g. remove all cells with percent mitochondrial genes above 40).

Calculate % mitochondrial genes per cell

seur[["perc.mito"]] <- PercentageFeatureSet(seur, pattern = "^mt-")
summary(seur$perc.mito)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.000   3.179   4.701   7.913   6.769  96.575 

- UMI counts per cell

outliers=c()
for ( i in unique(seur$sample)){
  outliers=c(outliers, 
             scater::isOutlier(seur$nCount_RNA[seur$sample==i], nmads=3, type="lower", log=TRUE)
             )
}
seur$nUMI.outlier.low <- outliers[colnames(seur)]
cat("Outliers:",sum(seur$nUMI.outlier.low))
Outliers: 1

Create histograms

for ( i in unique(seur$sample)){
  hist(seur$nCount_RNA[seur$sample==i],
        breaks = 100,xlab="nCount_RNA",
        main=paste0("Total UMI counts per cell: ",i))
  if(sum(seur$sample==i & seur$nUMI.outlier.low)!=0)
    abline(v = max(seur$nCount_RNA[seur$sample==i & seur$nUMI.outlier.low]), col = "red")
}

Create violin plots

for ( i in unique(seur$sample)){
 print(ggplot(as.data.frame(seur[[]])[seur$sample==i,], aes(1, nCount_RNA)) + 
    geom_violin(fill="gray80") +theme_classic()+ theme(axis.title.x = element_blank())+
    geom_jitter(height = 0, width = 0.3, aes(col=nUMI.outlier.low)) +
    scale_color_manual(values=c("#00BFC4", "#F8766D"))+ggtitle(paste0("Total UMI counts per cell: ",i)))
}

- Number of genes per cell

outliers=c()
for ( i in unique(seur$sample)){
  outliers=c(outliers, 
             scater::isOutlier(seur$nFeature_RNA[seur$sample==i], nmads=3, type="lower", log=TRUE)
             )
}
seur$nGene.outlier.low <- outliers[colnames(seur)]
cat("Outliers:",sum(seur$nGene.outlier.low))
Outliers: 1009

Create histograms

for ( i in unique(seur$sample)){
  hist(seur$nFeature_RNA[seur$sample==i],
        breaks = 100,xlab="nCount_RNA",
        main=paste0("Number of genes per cell: ",i))
  if(sum(seur$sample==i & seur$nGene.outlier.low)!=0)
    abline(v = max(seur$nFeature_RNA[seur$sample==i & seur$nGene.outlier.low]), col = "red")
}

Create violin plots

for ( i in unique(seur$sample)){
 print(ggplot(as.data.frame(seur[[]])[seur$sample==i,], aes(1, nFeature_RNA)) + 
    geom_violin(fill="gray80") +theme_classic()+ theme(axis.title.x = element_blank())+
    geom_jitter(height = 0, width = 0.3, aes(col=nGene.outlier.low)) +
    scale_color_manual(values=c("#00BFC4", "#F8766D"))+ggtitle(paste0("Number of genes per cell: ",i)))
}

- Proportion of mitohondrial genes per cell

outliers=c()
for ( i in unique(seur$sample)){
  outliers=c(outliers, 
             scater::isOutlier(seur$perc.mito[seur$sample==i], nmads=3, type="higher", log=TRUE)
             )
}
seur$mito.outlier.high <- outliers[colnames(seur)]
cat("Outliers:",sum(seur$mito.outlier.high))
Outliers: 1440

Create histograms

for ( i in unique(seur$sample)){
  hist(seur$perc.mito[seur$sample==i],
        breaks = 100,xlab="perc.mito",
        main=paste0("% mito genes per cell: ",i))
  if(sum(seur$sample==i & seur$mito.outlier.high)!=0)
    abline(v = min(seur$perc.mito[seur$sample==i & seur$mito.outlier.high]), col = "red")
}

Create violin plots

for ( i in unique(seur$sample)){
 print(ggplot(as.data.frame(seur[[]])[seur$sample==i,], aes(1, perc.mito)) + 
    geom_violin(fill="gray80") +theme_classic()+ theme(axis.title.x = element_blank())+
    geom_jitter(height = 0, width = 0.3, aes(col=mito.outlier.high)) +
    scale_color_manual(values=c("#00BFC4", "#F8766D"))+ggtitle(paste0("% mito genes per cell: ",i)))
}

Overlap of cells, outliers for UMI counts, number of genes and % mitochodrial genes per cell

A large nuber of cells, outliers specifically for one QC metric might be a concern and require fuurhter investigation

for ( i in unique(seur$sample)){
v <-venn.diagram(
  list (nUMI=colnames(seur)[seur$nUMI.outlier.low & seur$sample==i],
        nGene=colnames(seur)[seur$nGene.outlier.low & seur$sample==i],
    perc.mito=colnames(seur)[seur$mito.outlier.high & seur$sample==i]),
  filename=NULL,main=i,
  alpha = c( 0.5,0.5,0.5),
  fill = c("green","orange","blue")
)
grid.newpage()
grid.draw(v)
rm(v)
}

How many cells will be removed during the filtering

cells.to.keep= ! (seur$mito.outlier.high | seur$nGene.outlier.low | seur$nUMI.outlier.low)
print(paste(ncol(seur)- 
            ncol(seur[,cells.to.keep]),
     "cells to be removed"))
[1] "1524 cells to be removed"

Violin plots after filtering

for ( i in unique(seur$sample)){
  print(ggplot(as.data.frame(seur[[]][cells.to.keep& seur$sample==i, ]), aes(1, nCount_RNA)) + 
   geom_violin(fill="gray80") + theme_classic()+ theme(axis.title.x = element_blank())+
   geom_jitter(height = 0, width = 0.3, aes(col=nUMI.outlier.low)) +
   scale_color_manual(values=c("#00BFC4", "#F8766D"))+ggtitle(paste0("Total UMI counts per cell: ",i)))
}

for ( i in unique(seur$sample)){
  print(ggplot(as.data.frame(seur[[]][ cells.to.keep, ]), aes(1, nFeature_RNA)) + 
   geom_violin(fill="gray80") + theme_classic()+ theme(axis.title.x = element_blank())+
   geom_jitter(height = 0, width = 0.3, aes(col=nGene.outlier.low)) +
   scale_color_manual(values=c("#00BFC4", "#F8766D"))+ggtitle(paste0("Number of genes per cell: ",i)))
}

for ( i in unique(seur$sample)){
  print(ggplot(as.data.frame(seur[[]][cells.to.keep, ]), aes(1, perc.mito)) + 
   geom_violin(fill="gray80") + theme_classic()+ theme(axis.title.x = element_blank())+
   geom_jitter(height = 0, width = 0.3, aes(col=mito.outlier.high)) +
   scale_color_manual(values=c("#00BFC4", "#F8766D"))+ggtitle(paste0("% mito genes per cell: ",i)))
}

Filter the outlier cells

  seur_clean<-seur[, cells.to.keep]
  print(dim(seur))
[1] 28692 28284
  print(dim(seur_clean))
[1] 28692 26760

2.2) Get PCA outliers (Optional)

Get additional outliers, using multivariate outlier detection function of scater, based on PCA computed on QC metric data (uses the mvoutlier package)

sce=as.SingleCellExperiment(seur_clean)
##### Choose QC variables for authomatic outlier detection
selected_variables <- c("nCount_RNA", 
                        "nFeature_RNA", "perc.mito")
setdiff(selected_variables, colnames(colData(sce)))
character(0)
Detect outliers
outliers=c()
for ( i in unique(seur$sample)){
sce_sample=runColDataPCA(sce[,sce$sample==i], outliers=T, variables=selected_variables)
outliers=c(outliers,sce_sample$outlier)
}
sce$outliers=outliers
seur_clean <- AddMetaData(object = seur_clean, metadata = colData(sce)[colnames(seur_clean),"outliers"], col.name = "pca.outlier")
cat("Outliers:",sum(seur_clean$pca.outlier))
Outliers: 430
for ( i in unique(seur$sample)){
print(ggplot(as.data.frame(colData(sce)[sce$sample==i,]), aes(1, nCount_RNA)) + 
  geom_violin(fill="gray80") + theme_classic()+ theme(axis.title.x = element_blank())+
  geom_jitter(height = 0, width = 0.3, aes(col=outliers)) +
  scale_color_manual(values=c("#00BFC4", "#F8766D"))+ggtitle(paste0("Total UMI counts per cell: ",i)))
}

for ( i in unique(seur$sample)){
print(ggplot(as.data.frame(colData(sce)[sce$sample==i,]), aes(1, nFeature_RNA)) + 
  geom_violin(fill="gray80") + theme_classic()+ theme(axis.title.x = element_blank())+
  geom_jitter(height = 0, width = 0.3, aes(col=outliers)) +
  scale_color_manual(values=c("#00BFC4", "#F8766D"))+ggtitle(paste0("Number of genes per cell: ",i)))
}

  for ( i in unique(seur$sample)){
print(ggplot(as.data.frame(colData(sce)[sce$sample==i,]), aes(1, perc.mito)) + 
  geom_violin(fill="gray80") + theme_classic()+ theme(axis.title.x = element_blank())+
  geom_jitter(height = 0, width = 0.3, aes(col=outliers)) +
  scale_color_manual(values=c("#00BFC4", "#F8766D"))+ggtitle(paste0("% mito genes per cell: ",i)))
  }

As the number of additional PCA outliers is large, we keep those cells for now and will consider later if they need to be removed or not
### Remove the PCA outlier cells
# seur_clean<-seur_clean[, cells.to.keep]
# print(dim(seur_clean))

3) QC: GENES

To define a cutoff of lowly-abundant genes, we plot the distribution of log-means across all genes. The cutoff is placed in middle of the rectangular component of the graph before the peak.

thresholds<-c(0.005,0.005)
ave.counts=list()
for ( i in 1: length(unique(seur_clean$sample))) {
   ave.counts[[i]] <- rowMeans(as.matrix(GetAssayData(seur_clean, slot="counts")[,seur_clean$sample==unique(seur$sample)[i]]))
  hist(log10(ave.counts[[i]]), breaks=100, main=paste0("Histogram of mean UMI counts per gene: ",unique(seur$sample)[i]), col="grey80",
     xlab=expression(Log[10]~"mean count per gene"))
  abline(v=log10(thresholds[i]), col="blue", lwd=2, lty=2)
}

Number of genes to keep

[1] "KO1"

FALSE  TRUE 
16241 12451 
[1] "WT1"

FALSE  TRUE 
16471 12221 
Filter out the lowly-abundant genes that overlap between all samples
i=1
genes.filter=names(usegenes[[i]][! usegenes[[i]]])
for ( i in 2: length(unique(seur_clean$sample))) {
  genes.filter=intersect(genes.filter, names(usegenes[[i]][! usegenes[[i]]]))
}
usegenes.final=!rownames(seur_clean) %in% genes.filter
table(usegenes.final)
usegenes.final
FALSE  TRUE 
16021 12671 
seur_clean<-seur_clean[usegenes.final, ]

4) CALCULATE DOUBLET SCORE

We estimate doublet score per cell, using the scDblFinder package,which simulates artificial doublets from cell clusters. It is preferable to look for doublets separately for each sample

doublet.score<- scDblFinder::scDblFinder(as.SingleCellExperiment(seur_clean), samples="sample", BPPARAM=
BiocParallel::MulticoreParam(3),returnType="table")
seur_clean <- AddMetaData(object = seur_clean, metadata = doublet.score[colnames(seur_clean),"score"], col.name = "doublet.score")
seur_clean <- AddMetaData(object = seur_clean, metadata = doublet.score[colnames(seur_clean),"class"], col.name = "doublet.class")
table(seur_clean$doublet.class)

doublet singlet 
   3881   22879 
summary(seur_clean$doublet.score)
     Min.   1st Qu.    Median      Mean   3rd Qu.      Max. 
0.0000695 0.0078344 0.0356150 0.1956456 0.1480074 0.9999810 

5) NORMALIZATION, HVG DETECTION and PCA

##### Normalize data
seur_clean <- NormalizeData(seur_clean,verbose = F)
##### HVG detection 
seur_clean <- FindVariableFeatures(seur_clean,verbose=F)
##### Scale data per gene
seur_clean <- ScaleData(seur_clean,verbose=F)
##### PCA
seur_clean <- RunPCA(seur_clean, features =VariableFeatures(seur_clean) ,verbose=F)

Select PCs for downstream analysis of the dataset

Seurat provides a heuristic method to help us select PC components. It generates a ranking of PCs based on the percentage of variance explained by each of them

ElbowPlot(object = seur_clean,ndims =50)

Heatmap of the genes that drive each PC

DimHeatmap(seur_clean, dims = 1:30, cells = 5000, balanced = TRUE)

6) UMAP DIMENSIONALITY REDUCTION

Run Uniform Manifold Approximation and Projection (UMAP) dimensional reduction technique for visualisation of the data

### PC selection for downstream analysis
dims.use<-30
seur_clean <- RunUMAP(seur_clean, dims = 1:dims.use, verbose=F)

Visualise the UMAP plot, coloured by sample to check for batch effects

DimPlot(object = seur_clean, group.by = "sample",label=T, repel=T)

FeaturePlot(object = seur_clean, features = c("Ly6c2", "Ccr2"), split.by = "sample" )

The UMAP plot shows relatively good mixing by sample for most cells. Logically, monocytes are mainly present in Ccr2 WT cells, as visualized by Ccr2 and Ly6c2 expression.

7) BATCH CORRECTION

In case batch correction is necessary, it can be performed using the harmony package. Select a value for theta - diversity clustering penalty parameter (Default theta=2). Larger values of theta result in stronger integration, but can lead to over-correction)

theta.use<-1 # Here the theta parameter can be modified
seur_clean<-RunHarmony(seur_clean, group.by.vars="sample",theta =theta.use,  
                       plot_convergence = TRUE, reduction.save =paste0("harmonyTheta",theta.use),
                       reduction.key = paste0("harmonyTheta",theta.use,"_"), verbose=F) 

seur_clean <- RunUMAP(seur_clean,reduction =paste0("harmonyTheta",theta.use), 
                      dims = 1:dims.use,verbose =F,   reduction.name = paste0("umapHarmonyTheta",theta.use, "PC",dims.use), 
                      reduction.key = paste0("umapHarmonyTheta",theta.use, "PC",dims.use,"_"))  
### Visualise the harmony-corrected UMAP plot, coloured by sample to inspect if the batch effects were resolved
 DimPlot(object = seur_clean,reduction = paste0("umapHarmonyTheta",theta.use, "PC",dims.use), 
         group.by = "sample",label=T, repel=T)+ggtitle(paste0("Samples: UMAP Harmony theta= ",theta.use, " PC= ",dims.use))

As the the samples already show good mixing before batch correction, and the library preparation was done on the same day for both samples, we are going to continue without batch correction

Visualize QC metrics

FeaturePlot(object = seur_clean,
            features = c("nCount_RNA", "nFeature_RNA", "perc.mito","doublet.score"))

Visualize PCA outlier cells

DimPlot(object = seur_clean, group.by = "pca.outlier") 

Visualize QC metrics for pca outliers, and the remaining cells

FeaturePlot(seur_clean, split.by ="pca.outlier" ,features=c("nCount_RNA", "nFeature_RNA", "perc.mito", "Mki67","Stmn1"), 
keep.scale="feature", pt.size = 0.2)

The PCA outliers include cells with high percent mitochondrial genes, as well as cells with high UMI content and number of genes. Many of the latter express proliferation markers (Mki67, Stmn1), the active proliferation explaining their higher number of UMIs and genes. Therefore the PCA outliers will not be removed.

8) CLUSTERING

We run Louvain clustering with varying the clustering resolution between 0 and 2

seur_clean <- FindNeighbors(seur_clean, dims = 1:dims.use, 
                      graph.name = paste0("RNA_snn_PC",dims.use), verbose=F)
for ( i in seq(0,2, 0.25))
  seur_clean <- FindClusters(seur_clean, resolution = i, graph.name=paste0("RNA_snn_PC",dims.use), verbose=F)

If the batch correction is necessary for a specific dataset, clustering can be performed on the harmony-corrected PCA embeddings:

#seur_clean <- FindNeighbors(seur_clean, dims = 1:dims.use, reduction =paste0("harmonyTheta",theta.use), graph.name = paste0("RNA_snn_harmony_theta",theta.use, ".PC",dims.use), verbose=F)
#for ( i in seq(0,2, 0.25))
#  seur_clean <- FindClusters(seur_clean, resolution = i, graph.name=paste0("RNA_snn_harmony_theta",theta.use, ".PC",dims.use), verbose=F)

Choosing a clustering resolution value

Plot of a clustering tree showing the relationship between clustering at different resolution (using the clustree package).This plot allows us to see how are the clusters related to each other and which ones are stable across different resolutions

clustree(seur_clean, prefix = paste0("RNA_snn_PC",dims.use,"_res."))+
  ggtitle(paste("PC =",dims.use))
The `add` argument of `group_by()` is deprecated as of dplyr 1.0.0.
Please use the `.add` argument instead.
This warning is displayed once every 8 hours.
Call `lifecycle::last_warnings()` to see where this warning was generated.

The clusters are relatively stable until resolution of 1

Visualize the clusters for several resolution values on the UMAP plot

plot<-list()
for ( res in c(0.25,0.5, 0.75,1))
  plot[[as.character(res)]]<-DimPlot(seur_clean,label=T,repel=T, group.by = paste0("RNA_snn_PC",dims.use,"_res.",res))+
                                      ggtitle(paste("PC =",dims.use,"res=",res))
plot_grid(plotlist=plot)

9) ANNOTATING THE CLUSTERS

Let’s find differentially expressed (DE) genes per cluster for resolution=1.

res=1
Idents(seur_clean)=  paste0("RNA_snn_PC",dims.use,"_res.",res)
Idents(seur_clean)=  factor(Idents(seur_clean),levels = 0:(length(unique(Idents(seur_clean)))-1))
DEgenes<-FindAllMarkers(seur_clean,min.cells.group=2,pseudocount.use = 0.01, max.cells.per.ident = 1000,verbose = F)

Next,we visualise the expression of the top marker genes for each cluster in a heatmap

features.plot=DEgenes%>%filter(avg_logFC>0)%>% group_by(cluster)%>%top_n(n=-3,wt=p_val_adj)%>%pull(gene) ### top 3 markers
length(features.plot)
[1] 83
seur_clean=ScaleData(seur_clean, features = rownames(seur_clean)) ### Scale the data for all genes to be able to visualise their expression with DoHeatmap
DoHeatmap(seur_clean, features = features.plot, assay = "RNA", angle = 90, label =T, size=4) +
  scale_fill_gradient2(low = "blue", mid = "white",high = "red")+
  theme(legend.position = "bottom")%>% suppressMessages()
Scale for 'fill' is already present. Adding another scale for 'fill', which will replace the existing scale.

Check the distribution of each cluster per sample

data<-as.data.frame(table(seur_clean$sample, Idents(seur_clean)))
colnames(data)=c("sample","cluster","Freq")
ggplot(data, aes(x=cluster, y=Freq, fill=sample))+geom_bar(stat = "identity")+ggtitle(paste0("Number of cells per cluster (resolution =",res,")"))+theme_classic()

Visualise the expression of marker genes on a UMAP plot

FeaturePlot(seur_clean,features=c("Ccr2","Ly6c2","C1qa","Sparc","P2ry12","Tgfbi","Ms4a7","Mki67","Gata2","Hdc","Cpa3","S100a9","Csf3r","Flt3","Xcr1","Cd209a","H2-DMb2","Ccr7","Ccr9","Ms4a1","Jchain", "Cd3e","Cd8a","Cd4","Foxp3","Klrb1c","Sox2","Plp1"))

seur_clean$nCount_RNA_log=log2(seur_clean$nCount_RNA)
seur_clean$nFeature_RNA_log=log2(seur_clean$nFeature_RNA)

Visualise the expression of marker genes using a DotPlot

DotPlot(seur_clean,features=c("Ptprc","Ccr2","Ly6c2","Plac8","C1qa","Sparc","Sall1","Tgfbi","Ms4a7","Mki67","Stmn1","Mcm3","Ifit3","Ly6g","Csf3r","S100a9","S100a8","Flt3","Xcr1","Cd209a","H2-DMb2","Napsa","Siglech","Ccr9","Ccr7","Cacnb3","Ccl22", "Cd3e","Cd8a","Cd4","Lef1","Tcf7","Foxp3","Il2ra","Tcrg-C1","Trdc","Klrb1c","Ncr1","Gnly", "Cd19","Ms4a1","Sdc1","Jchain","Ms4a2", "Gata2","Cd200r3","Cdh1","Hgf","Hdc","Mcpt1","Cpa3","Sox2","Gjc3","Plp1", "doublet.score","perc.mito","nCount_RNA_log","nFeature_RNA_log"))+RotatedAxis()

Assign cell types to the clusters, based on the marker gene expression

Idents(seur_clean) <- plyr::mapvalues(x = Idents(seur_clean), from = 0:25, 
     to =c("TAM 1","CD8 T cells","TAM 2","TAM 3","TAM 4","Artefact","NK cells","cDC2","Basophils","Treg cells","TAM Proliferating","migDC","Monocytes","cDC1","T cells Proliferating","CD4 T cells 1","pDC","CD4 T cells 2","TAM 5","Plasma cells","B cells","TAM/T doublets","Mast cells","Neutrophils","Oligodendrocytes","TAM/Basophil doublets"))
DimPlot(seur_clean,repel =T,label=T) 

DotPlot(seur_clean,features=c("Ptprc","Ccr2","Ly6c2","Plac8","C1qa","Sparc","Sall1","Tgfbi","Ms4a7","Mki67","Stmn1","Mcm3","Ifit3","Ly6g","Csf3r","S100a9","S100a8","Flt3","Xcr1","Cd209a","H2-DMb2","Napsa","Siglech","Ccr9","Ccr7","Cacnb3","Ccl22", "Cd3e","Cd8a","Cd4","Lef1","Tcf7","Foxp3","Il2ra","Tcrg-C1","Trdc","Klrb1c","Ncr1","Gnly", "Cd19","Ms4a1","Sdc1","Jchain","Ms4a2", "Gata2","Cd200r3","Cdh1","Hgf","Hdc","Mcpt1","Cpa3","Sox2","Gjc3","Plp1", "doublet.score","perc.mito","nCount_RNA_log","nFeature_RNA_log"))+RotatedAxis()

10) REMOVE DOUBLETS AND ARTEFACTS

Remove the doublets and artefacts - cluster with high mitochodrial content and the oligodendrocytes as the latter are probably dues to impurities during the CD45+ sorting

data<-as.data.frame(table(seur_clean$doublet.class, Idents(seur_clean)))
colnames(data)=c("doublet.class","cluster","Freq")
ggplot(data, aes(x=cluster, y=Freq, fill=doublet.class))+geom_bar(stat = "identity")+ggtitle(paste0("Number of cells per cluster (resolution =",res,")"))+theme_classic()+RotatedAxis()

cells.keep=WhichCells(seur_clean, idents =c("Artefact","TAM/T doublets","TAM/Basophil doublets","Oligodendrocytes"), invert = T )
cat(ncol(seur_clean)-length(cells.keep),"cells to be removed")
2350 cells to be removed

Remove cells and rerun HVG detection,PCA and UMAP

seur_artefacts_removed=seur_clean[,cells.keep]
seur_artefacts_removed <- FindVariableFeatures(seur_artefacts_removed,verbose=F)
seur_artefacts_removed <- ScaleData(seur_artefacts_removed,verbose=F)
seur_artefacts_removed <- RunPCA(seur_artefacts_removed, features =VariableFeatures(seur_artefacts_removed) ,verbose=F)
#### Select PCs for downstream analysis of the dataset
ElbowPlot(object = seur_artefacts_removed,ndims =50)

Heatmap of the genes that drive each PC

DimHeatmap(seur_artefacts_removed, dims = 1:30, cells = 5000, balanced = TRUE)

Run UMAP

dims.use<-30 ### Final PC selection for downstream analysis
seur_artefacts_removed <- RunUMAP(seur_artefacts_removed, dims = 1:dims.use, verbose=F)
DimPlot(object = seur_artefacts_removed, group.by = "sample",label=T, repel=T)+
  ggtitle(paste0("Samples: UMAP PC= ",dims.use))

Explore DE genes between cluster TAM5 and TAM1 through Volcano plot

contrast.name="TAM5vsTAM1"
DEG_TAM5vsTAM1=FindMarkers(seur_artefacts_removed,ident.1 = "TAM 5",ident.2 = "TAM 1",
                           pseudocount.use = 0.01, verbose = F)
DEG_TAM5vsTAM1$gene=rownames(DEG_TAM5vsTAM1)
ggplot(data=DEG_TAM5vsTAM1, aes(x= avg_logFC, y = -log10(p_val_adj))) +
    geom_point(alpha = 1, size=2, 
               aes(col = p_val_adj<1e-20 & (avg_logFC>1 | avg_logFC< -1))) +
    scale_color_manual(values = c("dark grey", "brown1")) +
    geom_text_repel(data=subset(DEG_TAM5vsTAM1,  
                    p_val_adj<1e-100 & (avg_logFC>1 | avg_logFC< -1)),aes(label = gene), size =4)+
    theme_classic()+theme(legend.position = "none")+ggtitle(contrast.name)

Gene Ontology enrichment with Metascape of the genes upregulated in cluster TAM5 compared to cluster TAM1

Select top upregulated DEG (as those genes are specific for the cluster of interest). It is best to have at least 20-50 genes for GO enrichment.

p_tresh=1e-20
logFC_thresh=1
selected.genes=DEG_TAM5vsTAM1[DEG_TAM5vsTAM1$p_val_adj<p_tresh & DEG_TAM5vsTAM1$avg_logFC>logFC_thresh,"gene"]
length(selected.genes)
[1] 104

Export gene names as a csv file for metascape

write.table(selected.genes, file=paste0("Upregulated_genes_",contrast.name,"_p_val_adj.",p_tresh,"_logFC.",logFC_thresh,".csv"), row.names = F, col.names = F, sep=",")
Run GO enrichment in Metascape (https://metascape.org/):
Input the .csv file and do Express analysis with “Input as”= M.musculus and “analysis as”= M.musculus => Express Analysis. Save the “Gene List Report Excel Sheets”
Visualization of the enriched pathways on the Volcano plot:
GO.metascape=xlsx::read.xlsx( paste0("Metascape_Upregulated_genes_",contrast.name,"_p_val_adj.",p_tresh,"_logFC.",logFC_thresh,".xlsx"),2 )
#Select only the main (summary) pathways
GO.metascape$group=sapply(strsplit(GO.metascape$GroupID, "_"), "[[",2)

Top GO enriched pathway:

index=1
gset.symbols=GO.metascape[index,"Symbols"]
gset.symbols=strsplit(gset.symbols, ",")[[1]]
GOterm=GO.metascape[index,"Description"]
ggplot(data=DEG_TAM5vsTAM1, aes(x= avg_logFC, y = -log10(p_val_adj))) +
    geom_point(alpha = 1, size=2,
               aes(color=gene %in% gset.symbols)) +
    scale_color_manual(values = c("dark grey", "brown1")) +
    geom_text_repel(data=subset(DEG_TAM5vsTAM1,  
                   gene %in% gset.symbols),aes(label = gene), size =4)+
    theme_classic()+theme(legend.position = "none")+ggtitle(paste0( contrast.name, ": ", GOterm, " pathway"))

“response to hypoxia” pathway:

GOterm="response to hypoxia"
gset.symbols=GO.metascape[GO.metascape$Description==GOterm,"Symbols"]
gset.symbols=strsplit(gset.symbols, ",")[[1]]
ggplot(data=DEG_TAM5vsTAM1, aes(x= avg_logFC, y = -log10(p_val_adj))) +
    geom_point(alpha = 1, size=2,
               aes(color=gene %in% gset.symbols)) +
    scale_color_manual(values = c("dark grey", "brown1")) +
    geom_text_repel(data=subset(DEG_TAM5vsTAM1,  
                   gene %in% gset.symbols),aes(label = gene), size =4)+
    theme_classic()+theme(legend.position = "none")+ggtitle(paste0( contrast.name, ": ", GOterm, " pathway"))

DimPlot(object = seur_artefacts_removed,label=T, repel=T)+
  ggtitle(paste0("Cell annotation: UMAP PC= ",dims.use))

Save object
saveRDS(seur_clean, file="Citeseq_mouse_GBM.seuratObj.rds")
sessionInfo()
R version 4.0.3 (2020-10-10)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 20.04.4 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.9.0
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.9.0

locale:
 [1] LC_CTYPE=en_US.UTF-8          LC_NUMERIC=C                  LC_TIME=en_US.UTF-8           LC_COLLATE=en_US.UTF-8       
 [5] LC_MONETARY=en_US.UTF-8       LC_MESSAGES=en_US.UTF-8       LC_PAPER=en_US.UTF-8          LC_NAME=en_US.UTF-8          
 [9] LC_ADDRESS=en_US.UTF-8        LC_TELEPHONE=en_US.UTF-8      LC_MEASUREMENT=en_US.UTF-8    LC_IDENTIFICATION=en_US.UTF-8

attached base packages:
 [1] grid      parallel  stats4    stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] ggrepel_0.9.1               scDblFinder_1.4.0           cowplot_1.1.1               clustree_0.4.3             
 [5] ggraph_2.0.4                harmony_1.0                 Rcpp_1.0.6                  mvoutlier_2.0.9            
 [9] sgeostat_1.0-27             VennDiagram_1.6.20          futile.logger_1.4.3         dplyr_1.0.3                
[13] scater_1.18.3               ggplot2_3.3.4               SingleCellExperiment_1.12.0 SummarizedExperiment_1.20.0
[17] Biobase_2.50.0              GenomicRanges_1.42.0        GenomeInfoDb_1.26.7         IRanges_2.24.1             
[21] S4Vectors_0.28.1            BiocGenerics_0.36.1         MatrixGenerics_1.2.1        matrixStats_0.60.0         
[25] Seurat_3.2.3               

loaded via a namespace (and not attached):
  [1] scattermore_0.7           prabclus_2.3-2            GGally_2.1.2              tidyr_1.1.2               knitr_1.30               
  [6] irlba_2.3.3               DelayedArray_0.16.3       data.table_1.13.6         rpart_4.1-15              RCurl_1.98-1.4           
 [11] generics_0.1.0            lambda.r_1.2.4            RANN_2.6.1                proxy_0.4-24              future_1.21.0            
 [16] spatstat.data_2.1-0       httpuv_1.5.5              assertthat_0.2.1          viridis_0.5.1             xfun_0.20                
 [21] rJava_0.9-13              hms_1.0.0                 evaluate_0.14             promises_1.1.1            fansi_0.4.2              
 [26] DEoptimR_1.0-8            readxl_1.3.1              igraph_1.2.6              DBI_1.1.1                 htmlwidgets_1.5.3        
 [31] reshape_0.8.8             purrr_0.3.4               ellipsis_0.3.1            RSpectra_0.16-0           ks_1.13.1                
 [36] backports_1.2.1           sROC_0.1-2                deldir_1.0-6              sparseMatrixStats_1.2.0   vctrs_0.3.6              
 [41] ROCR_1.0-11               abind_1.4-5               withr_2.4.0               ggforce_0.3.2             robustbase_0.93-7        
 [46] checkmate_2.0.0           vcd_1.4-8                 sctransform_0.3.2         scran_1.18.3              mclust_5.4.7             
 [51] goftest_1.2-2             cluster_2.1.0             lazyeval_0.2.2            laeken_0.5.1              crayon_1.4.0             
 [56] labeling_0.4.2            edgeR_3.32.1              pkgconfig_2.0.3           zCompositions_1.3.4       tweenr_1.0.1             
 [61] nlme_3.1-149              vipor_0.4.5               nnet_7.3-14               rlang_0.4.10              globals_0.14.0           
 [66] diptest_0.75-7            lifecycle_0.2.0           pls_2.7-3                 miniUI_0.1.1.1            rsvd_1.0.3               
 [71] cellranger_1.1.0          polyclip_1.10-0           lmtest_0.9-38             Matrix_1.3-2              carData_3.0-4            
 [76] boot_1.3-25               zoo_1.8-8                 beeswarm_0.2.3            ggridges_0.5.3            png_0.1-7                
 [81] viridisLite_0.3.0         bitops_1.0-7              KernSmooth_2.23-17        DelayedMatrixStats_1.12.2 stringr_1.4.0            
 [86] parallelly_1.23.0         beachmat_2.6.4            scales_1.1.1              magrittr_2.0.1            plyr_1.8.6               
 [91] ica_1.0-2                 zlibbioc_1.36.0           compiler_4.0.3            hdrcde_3.4                dqrng_0.2.1              
 [96] RColorBrewer_1.1-2        rrcov_1.5-5               fitdistrplus_1.1-3        cli_2.2.0                 XVector_0.30.0           
[101] listenv_0.8.0             patchwork_1.1.1           pbapply_1.4-3             formatR_1.7               MASS_7.3-53              
[106] mgcv_1.8-33               tidyselect_1.1.0          stringi_1.5.3             forcats_0.5.0             yaml_2.2.1               
[111] BiocSingular_1.6.0        locfit_1.5-9.4            tools_4.0.3               future.apply_1.7.0        rio_0.5.16               
[116] rstudioapi_0.13           bluster_1.0.0             foreign_0.8-79            gridExtra_2.3             farver_2.0.3             
[121] Rtsne_0.15                digest_0.6.27             shiny_1.5.0               pracma_2.3.3              fpc_2.2-9                
[126] car_3.0-10                xlsx_0.6.5                scuttle_1.0.4             later_1.1.0.1             RcppAnnoy_0.0.18         
[131] fda_5.1.9                 httr_1.4.2                kernlab_0.9-29            colorspace_2.0-0          tensor_1.5               
[136] rainbow_3.6               ranger_0.12.1             reticulate_1.18           truncnorm_1.0-8           splines_4.0.3            
[141] uwot_0.1.10               statmod_1.4.35            spatstat.utils_2.2-0      graphlayouts_0.7.1        xlsxjars_0.6.1           
[146] sp_1.4-5                  xgboost_1.3.2.1           flexmix_2.3-17            plotly_4.9.3              xtable_1.8-4             
[151] jsonlite_1.7.2            fds_1.8                   futile.options_1.0.1      spatstat_1.64-1           tidygraph_1.2.0          
[156] modeltools_0.2-23         R6_2.5.0                  NADA_1.6-1.1              pillar_1.4.7              htmltools_0.5.2          
[161] mime_0.9                  glue_1.4.2                fastmap_1.1.0             VIM_6.1.0                 BiocParallel_1.24.1      
[166] BiocNeighbors_1.8.2       cvTools_0.3.2             class_7.3-17              codetools_0.2-16          pcaPP_1.9-74             
[171] mvtnorm_1.1-1             lattice_0.20-41           tibble_3.0.5              curl_4.3                  ggbeeswarm_0.6.0         
[176] leiden_0.3.6              zip_2.1.1                 openxlsx_4.2.3            survival_3.2-7            limma_3.46.0             
[181] rmarkdown_2.6             munsell_0.5.0             e1071_1.7-6               GenomeInfoDbData_1.2.4    haven_2.3.1              
[186] reshape2_1.4.4            gtable_0.3.0              robCompositions_2.3.0    
LS0tCnRpdGxlOiAiU2luZ2xlLWNlbGwgUk5BIHNlcXVlbmNpbmcgZGF0YSBwcm9jZXNzaW5nIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKZGF0ZTogJ0NyZWF0ZWQgb246IGByIGZvcm1hdChTeXMuRGF0ZSgpLCAiJUIgJWQsICVZIilgJwotLS0KCkluIHRoaXMgZXhhbXBsZSwgd2UgYXJlIHVzaW5nIGEgQ0lURXNlcSBkYXRhc2V0IG9mIG1vdXNlIGdsaW9ibGFzdG9tYSBtb2RlbC5UaGlzIGRhdGFzZXQgY29udGFpbnMgQ0Q0NSsgc29ydGVkIGNlbGxzIGZyb20gYSBHTDI2MSBnbGlvYmxhc3RvbWEgdHVtb3Igb2Ygd2lsZCB0eXBlIGFuZCBDQ1IyIGtub2Nrb3V0IG1pY2UgKFdUNCBhbmQgS080KS5Zb3UgY2FuIGRvd25sb2FkIHRoZSBleHByZXNzaW9uIG1hdHJpeCBbaGVyZV0oaHR0cHM6Ly93d3cuYnJhaW5pbW11bmVhdGxhcy5vcmcvZGF0YV9maWxlcy90b0Rvd25sb2FkL2ZpbHRlcmVkX2ZlYXR1cmVfYmNfbWF0cml4X01vdXNlR0JNY2l0ZVNlcS56aXApLgoKYGBge3J9CnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7CiAgbGlicmFyeShTZXVyYXQpCiAgbGlicmFyeShzY2F0ZXIpCiAgbGlicmFyeShkcGx5cikKICBsaWJyYXJ5KFZlbm5EaWFncmFtKQogIGxpYnJhcnkobXZvdXRsaWVyKQogIGxpYnJhcnkoaGFybW9ueSkKICBsaWJyYXJ5KGNsdXN0cmVlKQogIGxpYnJhcnkoY293cGxvdCkKICBsaWJyYXJ5KHNjRGJsRmluZGVyKQogIGxpYnJhcnkoZ2dwbG90MikKICBsaWJyYXJ5KGdncmVwZWwpCn0pCmBgYAoKU2V0IHRoZSB3b3JraW5nIGRpcmVjdG9yeQpgYGB7ciBzZXR1cH0Ka25pdHI6Om9wdHNfa25pdCRzZXQocm9vdC5kaXIgPSAicGF0aC90by9kaXJlY3RvcnkiKQpgYGAKCiMjIyAxKSBMT0FESU5HIFRIRSBJTlBVVCBEQVRBCldlIHVzZSB0aGUgUmVhZDEwWCBmdW5jdGlvbiB0byByZWFkIGluIHRoZSBleHByZXNpb24gbWF0cml4IGZyb20gdGhlIENlbGwgUmFuZ2VyIG91dHB1dCwgd2hpY2ggcmV0dXJucyBhIGxpc3Qgb2YgdHdvIG1hdHJpY2VzOiAiR2VuZSBFeHByZXNzaW9uIiBhbmQgIkFudGlib2R5IENhcHR1cmUiLiBXZSBsb2FkIHRoZSAiR2VuZSBFeHByZXNzaW9uIiBtYXRyaXggaW50byBhIFNldXJhdCBvYmplY3QuCmBgYHtyfQpzZXVyPC1DcmVhdGVTZXVyYXRPYmplY3QoY291bnRzID0gUmVhZDEwWChwYXN0ZTAoImRhdGEvQ2l0ZXNlcV9tb3VzZV9HQk0vZmlsdGVyZWRfZmVhdHVyZV9iY19tYXRyaXgiKSlbWyJHZW5lIEV4cHJlc3Npb24iXV0gKQojIyMgRm9yIHNpbmdsZS1jZWxsIFJOQSBzZXF1ZW5jaW5nIGV4cGVyaW1lbnQgd2lob3V0IGZlYXR1cmUgYmFyY29kaW5nIGxpYnJhcnksIHVzZToKIyMjIHNldXI8LUNyZWF0ZVNldXJhdE9iamVjdChjb3VudHMgPSBSZWFkMTBYKHBhc3RlMCgicGF0aC90by9maWx0ZXJlZF9mZWF0dXJlX2JjX21hdHJpeCIpKSApCmBgYAoKSWRlbnRpZnkgdGhlIGRpZmZlcmVudCBzYW1wbGVzIGJhc2VkIG9uIHRoZSBjZWxsIGJhcmNvZGUgbmFtZXMKYGBge3J9CmhlYWQoY29sbmFtZXMoc2V1cikpCmBgYApgYGB7cn0Kc2V1ciRzYW1wbGU8LXNhcHBseShzdHJzcGxpdChjb2xuYW1lcyhzZXVyKSwgIi0iKSwgIltbIiwyKQp0YWJsZShzZXVyJHNhbXBsZSkKYGBgCmBgYHtyfQpzZXVyJHNhbXBsZTwtcGx5cjo6bWFwdmFsdWVzKHNldXIkc2FtcGxlLCBmcm9tPTE6MiwgdG89YygiS08xIiwiV1QxIikpCmBgYApBbHRlcm5hdGl2ZWx5LCBpZiBzdGFydGluZyBmcm9tIHRoZSBpbmRpdmlkdWFsIGV4cHJlc3Npb24gbWF0cmljZXMgZm9yIGVhY2ggc2FtcGxlLHdlIGNhbiBsb2FkIHRoZW0gYXMgYSBsaXN0IG9mIHNldXJhdCBvYmplY3RzIGFuZCB0aGVuIG1lcmdlIHRoZW0gdXNpbmcgdGhlIFNldXJhdCBmdW5jdGlvbiAibWVyZ2UiOgpgYGB7cn0KIyBzYW1wbGVfZm9sZGVyX25hbWVzPWMoIktPMSIsIldUMSIpCiMjIyBzYW1wbGVfZm9sZGVyX25hbWVzIC0gYSB2ZWN0b3Igd2l0aCB0aGUgbmFtZXMgb2YgdGhlIGZvbGRlcnMgdGhhdCBjb250YWluIHRoZSBDZWxscmFuZ2VyIG91dHB1dHMgZm9yIGVhY2ggc2FtcGxlCiMgc2V1cj1saXN0KCkKIyBmb3IgKCBpIGluIGMoc2FtcGxlX2ZvbGRlcl9uYW1lcykpIHsgIAojICAgc2V1cltbaV1dPC1DcmVhdGVTZXVyYXRPYmplY3QoY291bnRzID0gUmVhZDEwWChwYXN0ZTAoaSwiL2ZpbHRlcmVkX2ZlYXR1cmVfYmNfbWF0cml4IikpW1siR2VuZSBFeHByZXNzaW9uIl1dICkKIyAgIHNldXJbW2ldXSRzYW1wbGU9aQojIH0KIyBzZXVyPC1tZXJnZShzZXVyW1sxXV0sc2V1clsyOmxlbmd0aChzZXVyKV0pCmBgYAoKCgojIyMgMikgUUM6IENFTExTCiMjIyMgMi4xKSAgRmluZCBvdXRsaWVycyBmb3IgdG90YWwgVU1JIGNvdW50cywgbnVtYmVyIG9mIGdlbmVzIGFuZCBwZXJjZW50IG1pdG9ob25kcmlhbCBnZW5lcyBwZXIgY2VsbC4gCk91dGxpZXJzIGFyZSBkZXRlcm1pbmVkIGJhc2VkIG9uIHRoZSBtZWRpYW4gYWJzb2x1dGUgZGV2aWF0aW9uIChNQUQpLiBUaGUgZ29hbCBpcyB0byByZW1vdmUgdGhlIGxvbmcgdGFpbCBiZWZvcmUvYWZ0ZXIgdGhlIHBlYWsgaW4gdGhlIHNpdHJpYnV0aW9uIG9mIHRoZSBRQyBtZXRyaWMuIEl0IGlzIHBvc3NpYmxlIHRvIG1vZGlmeSB0aGUgbm1hZHMgcGFyYW1ldGVyIChtaW5pbXVtIG51bWJlciBvZiBNQURzIGF3YXkgZnJvbSBtZWRpYW4sIHJlcXVpcmVkIGZvciBhIHZhbHVlIHRvIGJlIGNhbGxlZCBhbiBvdXRsaWVyKSwgb3IgdG8gc2V0IHRoZSB0aHJlc2hvbGQgbWFudWFsbHkgKGUuZy4gcmVtb3ZlIGFsbCBjZWxscyB3aXRoIHBlcmNlbnQgbWl0b2Nob25kcmlhbCBnZW5lcyBhYm92ZSA0MCkuIAoKQ2FsY3VsYXRlICUgbWl0b2Nob25kcmlhbCBnZW5lcyBwZXIgY2VsbApgYGB7cn0Kc2V1cltbInBlcmMubWl0byJdXSA8LSBQZXJjZW50YWdlRmVhdHVyZVNldChzZXVyLCBwYXR0ZXJuID0gIl5tdC0iKSAjIyMgV2UgaW5jbHVkZSBhbGwgZ2VuZXMgc3RhcnRpbmcgd2l0aCAibXQtIiBmb3IgY2FsY3VsYXRpbmcgdGhpcyBRQyBzYXRpc3RpYy4gRGVwZW5kaW5nIG9uIHRoZSBvcmdhbmlzbSwgdGhlIHNlYXJjaCBwYXR0ZXJuIG1pZ2h0IG5lZWQgdG8gYmUgbW9kaWZpZWQsIGUuZy4gaW50byAiTVQtIiBmb3IgaHVtYW4Kc3VtbWFyeShzZXVyJHBlcmMubWl0bykKYGBgCgoKIyMjIyAtIFVNSSBjb3VudHMgcGVyIGNlbGwKYGBge3J9Cm91dGxpZXJzPWMoKQpmb3IgKCBpIGluIHVuaXF1ZShzZXVyJHNhbXBsZSkpewogIG91dGxpZXJzPWMob3V0bGllcnMsIAogICAgICAgICAgICAgc2NhdGVyOjppc091dGxpZXIoc2V1ciRuQ291bnRfUk5BW3NldXIkc2FtcGxlPT1pXSwgbm1hZHM9MywgdHlwZT0ibG93ZXIiLCBsb2c9VFJVRSkKICAgICAgICAgICAgICkKfQpzZXVyJG5VTUkub3V0bGllci5sb3cgPC0gb3V0bGllcnNbY29sbmFtZXMoc2V1cildCmNhdCgiT3V0bGllcnM6IixzdW0oc2V1ciRuVU1JLm91dGxpZXIubG93KSkKYGBgCgpDcmVhdGUgaGlzdG9ncmFtcwpgYGB7ciAsIGZpZy5oZWlnaHQgPSAzLjUsIGZpZy53aWR0aCA9IDEwfQpmb3IgKCBpIGluIHVuaXF1ZShzZXVyJHNhbXBsZSkpewogIGhpc3Qoc2V1ciRuQ291bnRfUk5BW3NldXIkc2FtcGxlPT1pXSwKICAgICAgICBicmVha3MgPSAxMDAseGxhYj0ibkNvdW50X1JOQSIsCiAgICAgICAgbWFpbj1wYXN0ZTAoIlRvdGFsIFVNSSBjb3VudHMgcGVyIGNlbGw6ICIsaSkpCiAgaWYoc3VtKHNldXIkc2FtcGxlPT1pICYgc2V1ciRuVU1JLm91dGxpZXIubG93KSE9MCkKICAgIGFibGluZSh2ID0gbWF4KHNldXIkbkNvdW50X1JOQVtzZXVyJHNhbXBsZT09aSAmIHNldXIkblVNSS5vdXRsaWVyLmxvd10pLCBjb2wgPSAicmVkIikKfQpgYGAKQ3JlYXRlIHZpb2xpbiBwbG90cyAKYGBge3IgLCBmaWcuaGVpZ2h0ID0gMy41LCBmaWcud2lkdGggPSAxMH0KZm9yICggaSBpbiB1bmlxdWUoc2V1ciRzYW1wbGUpKXsKIHByaW50KGdncGxvdChhcy5kYXRhLmZyYW1lKHNldXJbW11dKVtzZXVyJHNhbXBsZT09aSxdLCBhZXMoMSwgbkNvdW50X1JOQSkpICsgCiAgICBnZW9tX3Zpb2xpbihmaWxsPSJncmF5ODAiKSArdGhlbWVfY2xhc3NpYygpKyB0aGVtZShheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCkpKwogICAgZ2VvbV9qaXR0ZXIoaGVpZ2h0ID0gMCwgd2lkdGggPSAwLjMsIGFlcyhjb2w9blVNSS5vdXRsaWVyLmxvdykpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiIzAwQkZDNCIsICIjRjg3NjZEIikpK2dndGl0bGUocGFzdGUwKCJUb3RhbCBVTUkgY291bnRzIHBlciBjZWxsOiAiLGkpKSkKfQpgYGAKCgojIyMjIC0gTnVtYmVyIG9mIGdlbmVzIHBlciBjZWxsCmBgYHtyfQpvdXRsaWVycz1jKCkKZm9yICggaSBpbiB1bmlxdWUoc2V1ciRzYW1wbGUpKXsKICBvdXRsaWVycz1jKG91dGxpZXJzLCAKICAgICAgICAgICAgIHNjYXRlcjo6aXNPdXRsaWVyKHNldXIkbkZlYXR1cmVfUk5BW3NldXIkc2FtcGxlPT1pXSwgbm1hZHM9MywgdHlwZT0ibG93ZXIiLCBsb2c9VFJVRSkKICAgICAgICAgICAgICkKfQpzZXVyJG5HZW5lLm91dGxpZXIubG93IDwtIG91dGxpZXJzW2NvbG5hbWVzKHNldXIpXQpjYXQoIk91dGxpZXJzOiIsc3VtKHNldXIkbkdlbmUub3V0bGllci5sb3cpKQpgYGAKQ3JlYXRlIGhpc3RvZ3JhbXMKYGBge3IgLCBmaWcuaGVpZ2h0ID0gMy41LCBmaWcud2lkdGggPSAxMH0KZm9yICggaSBpbiB1bmlxdWUoc2V1ciRzYW1wbGUpKXsKICBoaXN0KHNldXIkbkZlYXR1cmVfUk5BW3NldXIkc2FtcGxlPT1pXSwKICAgICAgICBicmVha3MgPSAxMDAseGxhYj0ibkNvdW50X1JOQSIsCiAgICAgICAgbWFpbj1wYXN0ZTAoIk51bWJlciBvZiBnZW5lcyBwZXIgY2VsbDogIixpKSkKICBpZihzdW0oc2V1ciRzYW1wbGU9PWkgJiBzZXVyJG5HZW5lLm91dGxpZXIubG93KSE9MCkKICAgIGFibGluZSh2ID0gbWF4KHNldXIkbkZlYXR1cmVfUk5BW3NldXIkc2FtcGxlPT1pICYgc2V1ciRuR2VuZS5vdXRsaWVyLmxvd10pLCBjb2wgPSAicmVkIikKfQpgYGAKQ3JlYXRlIHZpb2xpbiBwbG90cyAKYGBge3IgLCBmaWcuaGVpZ2h0ID0gMy41LCBmaWcud2lkdGggPSAxMH0KZm9yICggaSBpbiB1bmlxdWUoc2V1ciRzYW1wbGUpKXsKIHByaW50KGdncGxvdChhcy5kYXRhLmZyYW1lKHNldXJbW11dKVtzZXVyJHNhbXBsZT09aSxdLCBhZXMoMSwgbkZlYXR1cmVfUk5BKSkgKyAKICAgIGdlb21fdmlvbGluKGZpbGw9ImdyYXk4MCIpICt0aGVtZV9jbGFzc2ljKCkrIHRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSkrCiAgICBnZW9tX2ppdHRlcihoZWlnaHQgPSAwLCB3aWR0aCA9IDAuMywgYWVzKGNvbD1uR2VuZS5vdXRsaWVyLmxvdykpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiIzAwQkZDNCIsICIjRjg3NjZEIikpK2dndGl0bGUocGFzdGUwKCJOdW1iZXIgb2YgZ2VuZXMgcGVyIGNlbGw6ICIsaSkpKQp9CmBgYAoKIyMjIyAtIFByb3BvcnRpb24gb2YgbWl0b2hvbmRyaWFsIGdlbmVzIHBlciBjZWxsCmBgYHtyfQpvdXRsaWVycz1jKCkKZm9yICggaSBpbiB1bmlxdWUoc2V1ciRzYW1wbGUpKXsKICBvdXRsaWVycz1jKG91dGxpZXJzLCAKICAgICAgICAgICAgIHNjYXRlcjo6aXNPdXRsaWVyKHNldXIkcGVyYy5taXRvW3NldXIkc2FtcGxlPT1pXSwgbm1hZHM9MywgdHlwZT0iaGlnaGVyIiwgbG9nPVRSVUUpCiAgICAgICAgICAgICApCn0Kc2V1ciRtaXRvLm91dGxpZXIuaGlnaCA8LSBvdXRsaWVyc1tjb2xuYW1lcyhzZXVyKV0KY2F0KCJPdXRsaWVyczoiLHN1bShzZXVyJG1pdG8ub3V0bGllci5oaWdoKSkKYGBgCkNyZWF0ZSBoaXN0b2dyYW1zCmBgYHtyICwgZmlnLmhlaWdodCA9IDMuNSwgZmlnLndpZHRoID0gMTB9CmZvciAoIGkgaW4gdW5pcXVlKHNldXIkc2FtcGxlKSl7CiAgaGlzdChzZXVyJHBlcmMubWl0b1tzZXVyJHNhbXBsZT09aV0sCiAgICAgICAgYnJlYWtzID0gMTAwLHhsYWI9InBlcmMubWl0byIsCiAgICAgICAgbWFpbj1wYXN0ZTAoIiUgbWl0byBnZW5lcyBwZXIgY2VsbDogIixpKSkKICBpZihzdW0oc2V1ciRzYW1wbGU9PWkgJiBzZXVyJG1pdG8ub3V0bGllci5oaWdoKSE9MCkKICAgIGFibGluZSh2ID0gbWluKHNldXIkcGVyYy5taXRvW3NldXIkc2FtcGxlPT1pICYgc2V1ciRtaXRvLm91dGxpZXIuaGlnaF0pLCBjb2wgPSAicmVkIikKfQpgYGAKQ3JlYXRlIHZpb2xpbiBwbG90cyAKYGBge3IgLCBmaWcuaGVpZ2h0ID0gMy41LCBmaWcud2lkdGggPSAxMH0KZm9yICggaSBpbiB1bmlxdWUoc2V1ciRzYW1wbGUpKXsKIHByaW50KGdncGxvdChhcy5kYXRhLmZyYW1lKHNldXJbW11dKVtzZXVyJHNhbXBsZT09aSxdLCBhZXMoMSwgcGVyYy5taXRvKSkgKyAKICAgIGdlb21fdmlvbGluKGZpbGw9ImdyYXk4MCIpICt0aGVtZV9jbGFzc2ljKCkrIHRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSkrCiAgICBnZW9tX2ppdHRlcihoZWlnaHQgPSAwLCB3aWR0aCA9IDAuMywgYWVzKGNvbD1taXRvLm91dGxpZXIuaGlnaCkpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiIzAwQkZDNCIsICIjRjg3NjZEIikpK2dndGl0bGUocGFzdGUwKCIlIG1pdG8gZ2VuZXMgcGVyIGNlbGw6ICIsaSkpKQp9CmBgYAoKCgojIyMjIE92ZXJsYXAgb2YgY2VsbHMsIG91dGxpZXJzIGZvciBVTUkgY291bnRzLCBudW1iZXIgb2YgZ2VuZXMgYW5kICUgbWl0b2Nob2RyaWFsIGdlbmVzIHBlciBjZWxsCkEgbGFyZ2UgbnViZXIgb2YgY2VsbHMsIG91dGxpZXJzIHNwZWNpZmljYWxseSBmb3Igb25lIFFDIG1ldHJpYyBtaWdodCBiZSBhIGNvbmNlcm4gYW5kIHJlcXVpcmUgZnV1cmh0ZXIgaW52ZXN0aWdhdGlvbgpgYGB7ciAsIGZpZy5oZWlnaHQgPSAzLjUsIGZpZy53aWR0aCA9IDZ9CmZvciAoIGkgaW4gdW5pcXVlKHNldXIkc2FtcGxlKSl7CnYgPC12ZW5uLmRpYWdyYW0oCiAgbGlzdCAoblVNST1jb2xuYW1lcyhzZXVyKVtzZXVyJG5VTUkub3V0bGllci5sb3cgJiBzZXVyJHNhbXBsZT09aV0sCiAgICAgICAgbkdlbmU9Y29sbmFtZXMoc2V1cilbc2V1ciRuR2VuZS5vdXRsaWVyLmxvdyAmIHNldXIkc2FtcGxlPT1pXSwKICAgIHBlcmMubWl0bz1jb2xuYW1lcyhzZXVyKVtzZXVyJG1pdG8ub3V0bGllci5oaWdoICYgc2V1ciRzYW1wbGU9PWldKSwKICBmaWxlbmFtZT1OVUxMLG1haW49aSwKICBhbHBoYSA9IGMoIDAuNSwwLjUsMC41KSwKICBmaWxsID0gYygiZ3JlZW4iLCJvcmFuZ2UiLCJibHVlIikKKQpncmlkLm5ld3BhZ2UoKQpncmlkLmRyYXcodikKcm0odikKfQpgYGAKCgojIyMjIEhvdyBtYW55IGNlbGxzIHdpbGwgYmUgcmVtb3ZlZCBkdXJpbmcgdGhlIGZpbHRlcmluZwpgYGB7cix3YXJuaW5nPUZBTFNFfQpjZWxscy50by5rZWVwPSAhIChzZXVyJG1pdG8ub3V0bGllci5oaWdoIHwgc2V1ciRuR2VuZS5vdXRsaWVyLmxvdyB8IHNldXIkblVNSS5vdXRsaWVyLmxvdykKcHJpbnQocGFzdGUobmNvbChzZXVyKS0gCiAgICAgICAgICAgIG5jb2woc2V1clssY2VsbHMudG8ua2VlcF0pLAogICAgICJjZWxscyB0byBiZSByZW1vdmVkIikpCmBgYAoKIyMjIyBWaW9saW4gcGxvdHMgYWZ0ZXIgZmlsdGVyaW5nCmBgYHtyICwgZmlnLmhlaWdodCA9IDMuNSwgZmlnLndpZHRoID0gOH0KZm9yICggaSBpbiB1bmlxdWUoc2V1ciRzYW1wbGUpKXsKICBwcmludChnZ3Bsb3QoYXMuZGF0YS5mcmFtZShzZXVyW1tdXVtjZWxscy50by5rZWVwJiBzZXVyJHNhbXBsZT09aSwgXSksIGFlcygxLCBuQ291bnRfUk5BKSkgKyAKICAgZ2VvbV92aW9saW4oZmlsbD0iZ3JheTgwIikgKyB0aGVtZV9jbGFzc2ljKCkrIHRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSkrCiAgIGdlb21faml0dGVyKGhlaWdodCA9IDAsIHdpZHRoID0gMC4zLCBhZXMoY29sPW5VTUkub3V0bGllci5sb3cpKSArCiAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiIzAwQkZDNCIsICIjRjg3NjZEIikpK2dndGl0bGUocGFzdGUwKCJUb3RhbCBVTUkgY291bnRzIHBlciBjZWxsOiAiLGkpKSkKfQpmb3IgKCBpIGluIHVuaXF1ZShzZXVyJHNhbXBsZSkpewogIHByaW50KGdncGxvdChhcy5kYXRhLmZyYW1lKHNldXJbW11dWyBjZWxscy50by5rZWVwJiBzZXVyJHNhbXBsZT09aSwgXSksIGFlcygxLCBuRmVhdHVyZV9STkEpKSArIAogICBnZW9tX3Zpb2xpbihmaWxsPSJncmF5ODAiKSArIHRoZW1lX2NsYXNzaWMoKSsgdGhlbWUoYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpKSsKICAgZ2VvbV9qaXR0ZXIoaGVpZ2h0ID0gMCwgd2lkdGggPSAwLjMsIGFlcyhjb2w9bkdlbmUub3V0bGllci5sb3cpKSArCiAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiIzAwQkZDNCIsICIjRjg3NjZEIikpK2dndGl0bGUocGFzdGUwKCJOdW1iZXIgb2YgZ2VuZXMgcGVyIGNlbGw6ICIsaSkpKQp9CmZvciAoIGkgaW4gdW5pcXVlKHNldXIkc2FtcGxlKSl7CiAgcHJpbnQoZ2dwbG90KGFzLmRhdGEuZnJhbWUoc2V1cltbXV1bY2VsbHMudG8ua2VlcCYgc2V1ciRzYW1wbGU9PWksIF0pLCBhZXMoMSwgcGVyYy5taXRvKSkgKyAKICAgZ2VvbV92aW9saW4oZmlsbD0iZ3JheTgwIikgKyB0aGVtZV9jbGFzc2ljKCkrIHRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSkrCiAgIGdlb21faml0dGVyKGhlaWdodCA9IDAsIHdpZHRoID0gMC4zLCBhZXMoY29sPW1pdG8ub3V0bGllci5oaWdoKSkgKwogICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoIiMwMEJGQzQiLCAiI0Y4NzY2RCIpKStnZ3RpdGxlKHBhc3RlMCgiJSBtaXRvIGdlbmVzIHBlciBjZWxsOiAiLGkpKSkKfQpgYGAKCiMjIyMgRmlsdGVyIHRoZSBvdXRsaWVyIGNlbGxzCmBgYHtyfQogIHNldXJfY2xlYW48LXNldXJbLCBjZWxscy50by5rZWVwXQogIHByaW50KGRpbShzZXVyKSkKICBwcmludChkaW0oc2V1cl9jbGVhbikpCmBgYAoKCiMjIyMgMi4yKSBHZXQgUENBIG91dGxpZXJzIChPcHRpb25hbCkKR2V0IGFkZGl0aW9uYWwgb3V0bGllcnMsIHVzaW5nIG11bHRpdmFyaWF0ZSBvdXRsaWVyIGRldGVjdGlvbiBmdW5jdGlvbiBvZiBzY2F0ZXIsIGJhc2VkIG9uIFBDQSBjb21wdXRlZCBvbiBRQyBtZXRyaWMgZGF0YSAodXNlcyB0aGUgbXZvdXRsaWVyIHBhY2thZ2UpCgpgYGB7cn0Kc2NlPWFzLlNpbmdsZUNlbGxFeHBlcmltZW50KHNldXJfY2xlYW4pCiMjIyMjIENob29zZSBRQyB2YXJpYWJsZXMgZm9yIGF1dGhvbWF0aWMgb3V0bGllciBkZXRlY3Rpb24Kc2VsZWN0ZWRfdmFyaWFibGVzIDwtIGMoIm5Db3VudF9STkEiLCAKICAgICAgICAgICAgICAgICAgICAgICAgIm5GZWF0dXJlX1JOQSIsICJwZXJjLm1pdG8iKQpzZXRkaWZmKHNlbGVjdGVkX3ZhcmlhYmxlcywgY29sbmFtZXMoY29sRGF0YShzY2UpKSkgIyMjIGNoZWNrIGlmIGFueSBvZiB0aGUgc2VsZWN0ZWQgUUMgbWV0cmljcyBhcmUgbm90IHByZXNlbnQgaW4gdGhlIG1ldGFkYXRhCmBgYAojIyMjIyBEZXRlY3Qgb3V0bGllcnMKYGBge3J9Cm91dGxpZXJzPWMoKQpmb3IgKCBpIGluIHVuaXF1ZShzZXVyJHNhbXBsZSkpewpzY2Vfc2FtcGxlPXJ1bkNvbERhdGFQQ0Eoc2NlWyxzY2Ukc2FtcGxlPT1pXSwgb3V0bGllcnM9VCwgdmFyaWFibGVzPXNlbGVjdGVkX3ZhcmlhYmxlcykKb3V0bGllcnM9YyhvdXRsaWVycyxzY2Vfc2FtcGxlJG91dGxpZXIpCn0KYGBgCmBgYHtyfQpzY2Ukb3V0bGllcnM9b3V0bGllcnMKc2V1cl9jbGVhbiA8LSBBZGRNZXRhRGF0YShvYmplY3QgPSBzZXVyX2NsZWFuLCBtZXRhZGF0YSA9IGNvbERhdGEoc2NlKVtjb2xuYW1lcyhzZXVyX2NsZWFuKSwib3V0bGllcnMiXSwgY29sLm5hbWUgPSAicGNhLm91dGxpZXIiKQpjYXQoIk91dGxpZXJzOiIsc3VtKHNldXJfY2xlYW4kcGNhLm91dGxpZXIpKQpgYGAKYGBge3IgLCBmaWcuaGVpZ2h0ID0gMy41LCBmaWcud2lkdGggPSAxMH0KZm9yICggaSBpbiB1bmlxdWUoc2V1ciRzYW1wbGUpKXsKcHJpbnQoZ2dwbG90KGFzLmRhdGEuZnJhbWUoY29sRGF0YShzY2UpW3NjZSRzYW1wbGU9PWksXSksIGFlcygxLCBuQ291bnRfUk5BKSkgKyAKICBnZW9tX3Zpb2xpbihmaWxsPSJncmF5ODAiKSArIHRoZW1lX2NsYXNzaWMoKSsgdGhlbWUoYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpKSsKICBnZW9tX2ppdHRlcihoZWlnaHQgPSAwLCB3aWR0aCA9IDAuMywgYWVzKGNvbD1vdXRsaWVycykpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoIiMwMEJGQzQiLCAiI0Y4NzY2RCIpKStnZ3RpdGxlKHBhc3RlMCgiVG90YWwgVU1JIGNvdW50cyBwZXIgY2VsbDogIixpKSkpCn0KZm9yICggaSBpbiB1bmlxdWUoc2V1ciRzYW1wbGUpKXsKcHJpbnQoZ2dwbG90KGFzLmRhdGEuZnJhbWUoY29sRGF0YShzY2UpW3NjZSRzYW1wbGU9PWksXSksIGFlcygxLCBuRmVhdHVyZV9STkEpKSArIAogIGdlb21fdmlvbGluKGZpbGw9ImdyYXk4MCIpICsgdGhlbWVfY2xhc3NpYygpKyB0aGVtZShheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCkpKwogIGdlb21faml0dGVyKGhlaWdodCA9IDAsIHdpZHRoID0gMC4zLCBhZXMoY29sPW91dGxpZXJzKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiIzAwQkZDNCIsICIjRjg3NjZEIikpK2dndGl0bGUocGFzdGUwKCJOdW1iZXIgb2YgZ2VuZXMgcGVyIGNlbGw6ICIsaSkpKQp9CiAgZm9yICggaSBpbiB1bmlxdWUoc2V1ciRzYW1wbGUpKXsKcHJpbnQoZ2dwbG90KGFzLmRhdGEuZnJhbWUoY29sRGF0YShzY2UpW3NjZSRzYW1wbGU9PWksXSksIGFlcygxLCBwZXJjLm1pdG8pKSArIAogIGdlb21fdmlvbGluKGZpbGw9ImdyYXk4MCIpICsgdGhlbWVfY2xhc3NpYygpKyB0aGVtZShheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCkpKwogIGdlb21faml0dGVyKGhlaWdodCA9IDAsIHdpZHRoID0gMC4zLCBhZXMoY29sPW91dGxpZXJzKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiIzAwQkZDNCIsICIjRjg3NjZEIikpK2dndGl0bGUocGFzdGUwKCIlIG1pdG8gZ2VuZXMgcGVyIGNlbGw6ICIsaSkpKQogIH0KYGBgCgojIyMjIyBBcyB0aGUgbnVtYmVyIG9mIGFkZGl0aW9uYWwgUENBIG91dGxpZXJzIGlzIGxhcmdlLCB3ZSBrZWVwIHRob3NlIGNlbGxzIGZvciBub3cgYW5kIHdpbGwgY29uc2lkZXIgbGF0ZXIgaWYgdGhleSBuZWVkIHRvIGJlIHJlbW92ZWQgb3Igbm90CmBgYHtyfQojIyMgUmVtb3ZlIHRoZSBQQ0Egb3V0bGllciBjZWxscwojIHNldXJfY2xlYW48LXNldXJfY2xlYW5bLCBjZWxscy50by5rZWVwXQojIHByaW50KGRpbShzZXVyX2NsZWFuKSkKYGBgCgojIyMgMykgUUM6IEdFTkVTClRvIGRlZmluZSBhIGN1dG9mZiBvZiBsb3dseS1hYnVuZGFudCBnZW5lcywgd2UgcGxvdCB0aGUgZGlzdHJpYnV0aW9uIG9mIGxvZy1tZWFucyBhY3Jvc3MgYWxsIGdlbmVzLiBUaGUgY3V0b2ZmIGlzIHBsYWNlZCBpbiBtaWRkbGUgb2YgdGhlIHJlY3Rhbmd1bGFyIGNvbXBvbmVudCBvZiB0aGUgZ3JhcGggYmVmb3JlIHRoZSBwZWFrLgoKYGBge3IgLCBmaWcuaGVpZ2h0ID0gNCwgZmlnLndpZHRoID0gMTB9CnRocmVzaG9sZHM8LWMoMC4wMDUsMC4wMDUpCmF2ZS5jb3VudHM9bGlzdCgpCmZvciAoIGkgaW4gMTogbGVuZ3RoKHVuaXF1ZShzZXVyX2NsZWFuJHNhbXBsZSkpKSB7CiAgIGF2ZS5jb3VudHNbW2ldXSA8LSByb3dNZWFucyhhcy5tYXRyaXgoR2V0QXNzYXlEYXRhKHNldXJfY2xlYW4sIHNsb3Q9ImNvdW50cyIpWyxzZXVyX2NsZWFuJHNhbXBsZT09dW5pcXVlKHNldXIkc2FtcGxlKVtpXV0pKQogIGhpc3QobG9nMTAoYXZlLmNvdW50c1tbaV1dKSwgYnJlYWtzPTEwMCwgbWFpbj1wYXN0ZTAoIkhpc3RvZ3JhbSBvZiBtZWFuIFVNSSBjb3VudHMgcGVyIGdlbmU6ICIsdW5pcXVlKHNldXIkc2FtcGxlKVtpXSksIGNvbD0iZ3JleTgwIiwKICAgICB4bGFiPWV4cHJlc3Npb24oTG9nWzEwXX4ibWVhbiBjb3VudCBwZXIgZ2VuZSIpKQogIGFibGluZSh2PWxvZzEwKHRocmVzaG9sZHNbaV0pLCBjb2w9ImJsdWUiLCBsd2Q9MiwgbHR5PTIpCn0KYGBgCgpOdW1iZXIgb2YgZ2VuZXMgdG8ga2VlcApgYGB7cix3YXJuaW5nPUZBTFNFLCBlY2hvPUZBTFNFfQp1c2VnZW5lcz1saXN0KCkKZm9yICggaSBpbiAxOiBsZW5ndGgodW5pcXVlKHNldXJfY2xlYW4kc2FtcGxlKSkpIHsKICAgIHByaW50KHVuaXF1ZShzZXVyJHNhbXBsZSlbaV0pCiAgICB1c2VnZW5lc1tbaV1dPC1hdmUuY291bnRzW1tpXV0+dGhyZXNob2xkc1tpXQogICAgcHJpbnQodGFibGUodXNlZ2VuZXNbW2ldXSkpCn0KYGBgCgojIyMjIyBGaWx0ZXIgb3V0IHRoZSBsb3dseS1hYnVuZGFudCBnZW5lcyB0aGF0IG92ZXJsYXAgYmV0d2VlbiBhbGwgc2FtcGxlcwpgYGB7cn0KaT0xCmdlbmVzLmZpbHRlcj1uYW1lcyh1c2VnZW5lc1tbaV1dWyEgdXNlZ2VuZXNbW2ldXV0pCmZvciAoIGkgaW4gMjogbGVuZ3RoKHVuaXF1ZShzZXVyX2NsZWFuJHNhbXBsZSkpKSB7CiAgZ2VuZXMuZmlsdGVyPWludGVyc2VjdChnZW5lcy5maWx0ZXIsIG5hbWVzKHVzZWdlbmVzW1tpXV1bISB1c2VnZW5lc1tbaV1dXSkpCn0KdXNlZ2VuZXMuZmluYWw9IXJvd25hbWVzKHNldXJfY2xlYW4pICVpbiUgZ2VuZXMuZmlsdGVyCnRhYmxlKHVzZWdlbmVzLmZpbmFsKQpgYGAKYGBge3J9CnNldXJfY2xlYW48LXNldXJfY2xlYW5bdXNlZ2VuZXMuZmluYWwsIF0KYGBgCgojIyMgNCkgQ0FMQ1VMQVRFIERPVUJMRVQgU0NPUkUKV2UgZXN0aW1hdGUgZG91YmxldCBzY29yZSBwZXIgY2VsbCwgdXNpbmcgdGhlIHNjRGJsRmluZGVyIHBhY2thZ2Usd2hpY2ggc2ltdWxhdGVzIGFydGlmaWNpYWwgZG91YmxldHMgZnJvbSBjZWxsIGNsdXN0ZXJzLgpJdCBpcyBwcmVmZXJhYmxlIHRvIGxvb2sgZm9yIGRvdWJsZXRzIHNlcGFyYXRlbHkgZm9yIGVhY2ggc2FtcGxlCmBgYHtyfQpkb3VibGV0LnNjb3JlPC0gc2NEYmxGaW5kZXI6OnNjRGJsRmluZGVyKGFzLlNpbmdsZUNlbGxFeHBlcmltZW50KHNldXJfY2xlYW4pLCBzYW1wbGVzPSJzYW1wbGUiLCBCUFBBUkFNPQpCaW9jUGFyYWxsZWw6Ok11bHRpY29yZVBhcmFtKDMpLHJldHVyblR5cGU9InRhYmxlIikgIyMjIFVzaW5nIDMgY29yZXMKc2V1cl9jbGVhbiA8LSBBZGRNZXRhRGF0YShvYmplY3QgPSBzZXVyX2NsZWFuLCBtZXRhZGF0YSA9IGRvdWJsZXQuc2NvcmVbY29sbmFtZXMoc2V1cl9jbGVhbiksInNjb3JlIl0sIGNvbC5uYW1lID0gImRvdWJsZXQuc2NvcmUiKQpzZXVyX2NsZWFuIDwtIEFkZE1ldGFEYXRhKG9iamVjdCA9IHNldXJfY2xlYW4sIG1ldGFkYXRhID0gZG91YmxldC5zY29yZVtjb2xuYW1lcyhzZXVyX2NsZWFuKSwiY2xhc3MiXSwgY29sLm5hbWUgPSAiZG91YmxldC5jbGFzcyIpCmBgYApgYGB7ciB9CnRhYmxlKHNldXJfY2xlYW4kZG91YmxldC5jbGFzcykKYGBgCmBgYHtyIH0Kc3VtbWFyeShzZXVyX2NsZWFuJGRvdWJsZXQuc2NvcmUpCmBgYAoKIyMjIDUpIE5PUk1BTElaQVRJT04sIEhWRyBERVRFQ1RJT04gYW5kIFBDQQpgYGB7cn0KIyMjIyMgTm9ybWFsaXplIGRhdGEKc2V1cl9jbGVhbiA8LSBOb3JtYWxpemVEYXRhKHNldXJfY2xlYW4sdmVyYm9zZSA9IEYpCiMjIyMjIEhWRyBkZXRlY3Rpb24gCnNldXJfY2xlYW4gPC0gRmluZFZhcmlhYmxlRmVhdHVyZXMoc2V1cl9jbGVhbix2ZXJib3NlPUYpCiMjIyMjIFNjYWxlIGRhdGEgcGVyIGdlbmUKc2V1cl9jbGVhbiA8LSBTY2FsZURhdGEoc2V1cl9jbGVhbix2ZXJib3NlPUYpCiMjIyMjIFBDQQpzZXVyX2NsZWFuIDwtIFJ1blBDQShzZXVyX2NsZWFuLCBmZWF0dXJlcyA9VmFyaWFibGVGZWF0dXJlcyhzZXVyX2NsZWFuKSAsdmVyYm9zZT1GKQpgYGAKIyMjIyBTZWxlY3QgUENzIGZvciBkb3duc3RyZWFtIGFuYWx5c2lzIG9mIHRoZSBkYXRhc2V0ClNldXJhdCBwcm92aWRlcyBhIGhldXJpc3RpYyBtZXRob2QgdG8gaGVscCB1cyBzZWxlY3QgUEMgY29tcG9uZW50cy4gSXQgZ2VuZXJhdGVzIGEgcmFua2luZyBvZiBQQ3MgYmFzZWQgb24gdGhlIHBlcmNlbnRhZ2Ugb2YgdmFyaWFuY2UgZXhwbGFpbmVkIGJ5IGVhY2ggb2YgdGhlbSAKYGBge3IgLCBmaWcuaGVpZ2h0ID0gNCwgZmlnLndpZHRoID0gNix3YXJuaW5nPUZBTFNFfQpFbGJvd1Bsb3Qob2JqZWN0ID0gc2V1cl9jbGVhbixuZGltcyA9NTApCmBgYApIZWF0bWFwIG9mIHRoZSBnZW5lcyB0aGF0IGRyaXZlIGVhY2ggUEMKYGBge3IgLCBmaWcuaGVpZ2h0ID0gMTUsIGZpZy53aWR0aCA9IDEyLHdhcm5pbmc9RkFMU0V9CkRpbUhlYXRtYXAoc2V1cl9jbGVhbiwgZGltcyA9IDE6MzAsIGNlbGxzID0gNTAwMCwgYmFsYW5jZWQgPSBUUlVFKQpgYGAKCiMjIyA2KSBVTUFQIERJTUVOU0lPTkFMSVRZIFJFRFVDVElPTgpSdW4gVW5pZm9ybSBNYW5pZm9sZCBBcHByb3hpbWF0aW9uIGFuZCBQcm9qZWN0aW9uIChVTUFQKSBkaW1lbnNpb25hbCByZWR1Y3Rpb24gdGVjaG5pcXVlIGZvciB2aXN1YWxpc2F0aW9uIG9mIHRoZSBkYXRhCgpgYGB7cn0KIyMjIFBDIHNlbGVjdGlvbiBmb3IgZG93bnN0cmVhbSBhbmFseXNpcwpkaW1zLnVzZTwtMzAKYGBgCgpgYGB7cix3YXJuaW5nPUZBTFNFfQpzZXVyX2NsZWFuIDwtIFJ1blVNQVAoc2V1cl9jbGVhbiwgZGltcyA9IDE6ZGltcy51c2UsIHZlcmJvc2U9RikKYGBgCgpWaXN1YWxpc2UgdGhlIFVNQVAgcGxvdCwgY29sb3VyZWQgYnkgc2FtcGxlIHRvIGNoZWNrIGZvciBiYXRjaCBlZmZlY3RzCmBgYHtyICwgZmlnLmhlaWdodCA9IDYsIGZpZy53aWR0aCA9IDd9CkRpbVBsb3Qob2JqZWN0ID0gc2V1cl9jbGVhbiwgZ3JvdXAuYnkgPSAic2FtcGxlIixsYWJlbD1ULCByZXBlbD1UKQpgYGAKYGBge3IgLCBmaWcuaGVpZ2h0ID0gNiwgZmlnLndpZHRoID0gN30KRmVhdHVyZVBsb3Qob2JqZWN0ID0gc2V1cl9jbGVhbiwgZmVhdHVyZXMgPSBjKCJMeTZjMiIsICJDY3IyIiksIHNwbGl0LmJ5ID0gInNhbXBsZSIgKQpgYGAKVGhlIFVNQVAgcGxvdCBzaG93cyByZWxhdGl2ZWx5IGdvb2QgbWl4aW5nIGJ5IHNhbXBsZSBmb3IgbW9zdCBjZWxscy4gTG9naWNhbGx5LCBtb25vY3l0ZXMgYXJlIG1haW5seSBwcmVzZW50IGluIENjcjIgV1QgY2VsbHMsIGFzIHZpc3VhbGl6ZWQgYnkgQ2NyMiBhbmQgTHk2YzIgZXhwcmVzc2lvbi4KCiMjIyA3KSBCQVRDSCBDT1JSRUNUSU9OIApJbiBjYXNlIGJhdGNoIGNvcnJlY3Rpb24gaXMgbmVjZXNzYXJ5LCBpdCBjYW4gYmUgcGVyZm9ybWVkIHVzaW5nIHRoZSBoYXJtb255IHBhY2thZ2UuIFNlbGVjdCBhIHZhbHVlIGZvciB0aGV0YSAtIGRpdmVyc2l0eSBjbHVzdGVyaW5nIHBlbmFsdHkgcGFyYW1ldGVyIChEZWZhdWx0IHRoZXRhPTIpLiBMYXJnZXIgdmFsdWVzIG9mIHRoZXRhIHJlc3VsdCBpbiBzdHJvbmdlciBpbnRlZ3JhdGlvbiwgYnV0IGNhbiBsZWFkIHRvIG92ZXItY29ycmVjdGlvbikKYGBge3IgLCBmaWcuaGVpZ2h0ID0gNCwgZmlnLndpZHRoID0gNH0KdGhldGEudXNlPC0xICMgSGVyZSB0aGUgdGhldGEgcGFyYW1ldGVyIGNhbiBiZSBtb2RpZmllZApzZXVyX2NsZWFuPC1SdW5IYXJtb255KHNldXJfY2xlYW4sIGdyb3VwLmJ5LnZhcnM9InNhbXBsZSIsdGhldGEgPXRoZXRhLnVzZSwgIAogICAgICAgICAgICAgICAgICAgICAgIHBsb3RfY29udmVyZ2VuY2UgPSBUUlVFLCByZWR1Y3Rpb24uc2F2ZSA9cGFzdGUwKCJoYXJtb255VGhldGEiLHRoZXRhLnVzZSksCiAgICAgICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uLmtleSA9IHBhc3RlMCgiaGFybW9ueVRoZXRhIix0aGV0YS51c2UsIl8iKSwgdmVyYm9zZT1GKSAKc2V1cl9jbGVhbiA8LSBSdW5VTUFQKHNldXJfY2xlYW4scmVkdWN0aW9uID1wYXN0ZTAoImhhcm1vbnlUaGV0YSIsdGhldGEudXNlKSwgCiAgICAgICAgICAgICAgICAgICAgICBkaW1zID0gMTpkaW1zLnVzZSx2ZXJib3NlID1GLCAgIHJlZHVjdGlvbi5uYW1lID0gcGFzdGUwKCJ1bWFwSGFybW9ueVRoZXRhIix0aGV0YS51c2UsICJQQyIsZGltcy51c2UpLCAKICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbi5rZXkgPSBwYXN0ZTAoInVtYXBIYXJtb255VGhldGEiLHRoZXRhLnVzZSwgIlBDIixkaW1zLnVzZSwiXyIpKSAgCmBgYAoKYGBge3IgLCBmaWcuaGVpZ2h0ID0gNiwgZmlnLndpZHRoID0gN30KIyMjIFZpc3VhbGlzZSB0aGUgaGFybW9ueS1jb3JyZWN0ZWQgVU1BUCBwbG90LCBjb2xvdXJlZCBieSBzYW1wbGUgdG8gaW5zcGVjdCBpZiB0aGUgYmF0Y2ggZWZmZWN0cyB3ZXJlIHJlc29sdmVkCiBEaW1QbG90KG9iamVjdCA9IHNldXJfY2xlYW4scmVkdWN0aW9uID0gcGFzdGUwKCJ1bWFwSGFybW9ueVRoZXRhIix0aGV0YS51c2UsICJQQyIsZGltcy51c2UpLCAKICAgICAgICAgZ3JvdXAuYnkgPSAic2FtcGxlIixsYWJlbD1ULCByZXBlbD1UKStnZ3RpdGxlKHBhc3RlMCgiU2FtcGxlczogVU1BUCBIYXJtb255IHRoZXRhPSAiLHRoZXRhLnVzZSwgIiBQQz0gIixkaW1zLnVzZSkpCmBgYApBcyB0aGUgdGhlIHNhbXBsZXMgYWxyZWFkeSBzaG93IGdvb2QgbWl4aW5nIGJlZm9yZSBiYXRjaCBjb3JyZWN0aW9uLCBhbmQgdGhlIGxpYnJhcnkgcHJlcGFyYXRpb24gd2FzIGRvbmUgb24gdGhlIHNhbWUgZGF5IGZvciBib3RoIHNhbXBsZXMsIHdlIGFyZSBnb2luZyB0byBjb250aW51ZSB3aXRob3V0IGJhdGNoIGNvcnJlY3Rpb24KCgojIyMjIFZpc3VhbGl6ZSBRQyBtZXRyaWNzCmBgYHtyICwgZmlnLmhlaWdodCA9OCwgZmlnLndpZHRoID0gMTAsd2FybmluZz1GQUxTRX0KRmVhdHVyZVBsb3Qob2JqZWN0ID0gc2V1cl9jbGVhbiwKICAgICAgICAgICAgZmVhdHVyZXMgPSBjKCJuQ291bnRfUk5BIiwgIm5GZWF0dXJlX1JOQSIsICJwZXJjLm1pdG8iLCJkb3VibGV0LnNjb3JlIikpCmBgYApWaXN1YWxpemUgUENBIG91dGxpZXIgY2VsbHMKYGBge3IgLCBmaWcuaGVpZ2h0ID0gNCwgZmlnLndpZHRoID01LHdhcm5pbmc9RkFMU0V9CkRpbVBsb3Qob2JqZWN0ID0gc2V1cl9jbGVhbiwgZ3JvdXAuYnkgPSAicGNhLm91dGxpZXIiKSAKYGBgCiMjIyMgVmlzdWFsaXplIFFDIG1ldHJpY3MgZm9yIHBjYSBvdXRsaWVycywgYW5kIHRoZSByZW1haW5pbmcgY2VsbHMKYGBge3IgLCBmaWcuaGVpZ2h0ID0xMCwgZmlnLndpZHRoID03LHdhcm5pbmc9RkFMU0V9CkZlYXR1cmVQbG90KHNldXJfY2xlYW4sIHNwbGl0LmJ5ID0icGNhLm91dGxpZXIiICxmZWF0dXJlcz1jKCJuQ291bnRfUk5BIiwgIm5GZWF0dXJlX1JOQSIsICJwZXJjLm1pdG8iLCAiTWtpNjciLCJTdG1uMSIpLCAKa2VlcC5zY2FsZT0iZmVhdHVyZSIsIHB0LnNpemUgPSAwLjIpCmBgYApUaGUgUENBIG91dGxpZXJzIGluY2x1ZGUgY2VsbHMgd2l0aCBoaWdoIHBlcmNlbnQgbWl0b2Nob25kcmlhbCBnZW5lcywgYXMgd2VsbCBhcyBjZWxscyB3aXRoIGhpZ2ggVU1JIGNvbnRlbnQgYW5kIG51bWJlciBvZiBnZW5lcy4gTWFueSBvZiB0aGUgbGF0dGVyIGV4cHJlc3MgcHJvbGlmZXJhdGlvbiBtYXJrZXJzIChNa2k2NywgU3RtbjEpLCB0aGUgYWN0aXZlIHByb2xpZmVyYXRpb24gZXhwbGFpbmluZyB0aGVpciBoaWdoZXIgbnVtYmVyIG9mIFVNSXMgYW5kIGdlbmVzLiBUaGVyZWZvcmUgdGhlIFBDQSBvdXRsaWVycyB3aWxsIG5vdCBiZSByZW1vdmVkLgoKCgojIyMgOCkgQ0xVU1RFUklORwpXZSBydW4gTG91dmFpbiBjbHVzdGVyaW5nIHdpdGggdmFyeWluZyB0aGUgY2x1c3RlcmluZyByZXNvbHV0aW9uIGJldHdlZW4gMCBhbmQgMgpgYGB7cn0Kc2V1cl9jbGVhbiA8LSBGaW5kTmVpZ2hib3JzKHNldXJfY2xlYW4sIGRpbXMgPSAxOmRpbXMudXNlLCAKICAgICAgICAgICAgICAgICAgICAgIGdyYXBoLm5hbWUgPSBwYXN0ZTAoIlJOQV9zbm5fUEMiLGRpbXMudXNlKSwgdmVyYm9zZT1GKQpmb3IgKCBpIGluIHNlcSgwLDIsIDAuMjUpKQogIHNldXJfY2xlYW4gPC0gRmluZENsdXN0ZXJzKHNldXJfY2xlYW4sIHJlc29sdXRpb24gPSBpLCBncmFwaC5uYW1lPXBhc3RlMCgiUk5BX3Nubl9QQyIsZGltcy51c2UpLCB2ZXJib3NlPUYpCmBgYAoKSWYgdGhlIGJhdGNoIGNvcnJlY3Rpb24gaXMgbmVjZXNzYXJ5IGZvciBhIHNwZWNpZmljIGRhdGFzZXQsIGNsdXN0ZXJpbmcgY2FuIGJlIHBlcmZvcm1lZCBvbiB0aGUgaGFybW9ueS1jb3JyZWN0ZWQgUENBIGVtYmVkZGluZ3M6CmBgYHtyfQojc2V1cl9jbGVhbiA8LSBGaW5kTmVpZ2hib3JzKHNldXJfY2xlYW4sIGRpbXMgPSAxOmRpbXMudXNlLCByZWR1Y3Rpb24gPXBhc3RlMCgiaGFybW9ueVRoZXRhIix0aGV0YS51c2UpLCBncmFwaC5uYW1lID0gcGFzdGUwKCJSTkFfc25uX2hhcm1vbnlfdGhldGEiLHRoZXRhLnVzZSwgIi5QQyIsZGltcy51c2UpLCB2ZXJib3NlPUYpCiNmb3IgKCBpIGluIHNlcSgwLDIsIDAuMjUpKQojICBzZXVyX2NsZWFuIDwtIEZpbmRDbHVzdGVycyhzZXVyX2NsZWFuLCByZXNvbHV0aW9uID0gaSwgZ3JhcGgubmFtZT1wYXN0ZTAoIlJOQV9zbm5faGFybW9ueV90aGV0YSIsdGhldGEudXNlLCAiLlBDIixkaW1zLnVzZSksIHZlcmJvc2U9RikKYGBgCgojIyMjIENob29zaW5nIGEgY2x1c3RlcmluZyByZXNvbHV0aW9uIHZhbHVlClBsb3Qgb2YgYSBjbHVzdGVyaW5nIHRyZWUgc2hvd2luZyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gY2x1c3RlcmluZyBhdCBkaWZmZXJlbnQgcmVzb2x1dGlvbiAodXNpbmcgdGhlIGNsdXN0cmVlIHBhY2thZ2UpLlRoaXMgcGxvdCBhbGxvd3MgdXMgdG8gc2VlIGhvdyBhcmUgdGhlIGNsdXN0ZXJzIHJlbGF0ZWQgdG8gZWFjaCBvdGhlciBhbmQgd2hpY2ggb25lcyBhcmUgc3RhYmxlIGFjcm9zcyBkaWZmZXJlbnQgcmVzb2x1dGlvbnMKYGBge3IgLCBmaWcuaGVpZ2h0ID0gOCwgZmlnLndpZHRoID0gMTB9CmNsdXN0cmVlKHNldXJfY2xlYW4sIHByZWZpeCA9IHBhc3RlMCgiUk5BX3Nubl9QQyIsZGltcy51c2UsIl9yZXMuIikpKwogIGdndGl0bGUocGFzdGUoIlBDID0iLGRpbXMudXNlKSkKYGBgClRoZSBjbHVzdGVycyBhcmUgcmVsYXRpdmVseSBzdGFibGUgdW50aWwgcmVzb2x1dGlvbiBvZiAxCgojIyMjIFZpc3VhbGl6ZSB0aGUgY2x1c3RlcnMgZm9yIHNldmVyYWwgcmVzb2x1dGlvbiB2YWx1ZXMgb24gdGhlIFVNQVAgcGxvdApgYGB7ciAsIGZpZy5oZWlnaHQgPSA4LCBmaWcud2lkdGggPSAxMn0KcGxvdDwtbGlzdCgpCmZvciAoIHJlcyBpbiBjKDAuMjUsMC41LCAwLjc1LDEpKQogIHBsb3RbW2FzLmNoYXJhY3RlcihyZXMpXV08LURpbVBsb3Qoc2V1cl9jbGVhbixsYWJlbD1ULHJlcGVsPVQsIGdyb3VwLmJ5ID0gcGFzdGUwKCJSTkFfc25uX1BDIixkaW1zLnVzZSwiX3Jlcy4iLHJlcykpKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdndGl0bGUocGFzdGUoIlBDID0iLGRpbXMudXNlLCJyZXM9IixyZXMpKQpwbG90X2dyaWQocGxvdGxpc3Q9cGxvdCkKYGBgCgojIyMgIDkpIEFOTk9UQVRJTkcgVEhFIENMVVNURVJTCkxldCdzIGZpbmQgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIChERSkgZ2VuZXMgcGVyIGNsdXN0ZXIgZm9yIHJlc29sdXRpb249MS4gCmBgYHtyfQpyZXM9MQpJZGVudHMoc2V1cl9jbGVhbik9ICBwYXN0ZTAoIlJOQV9zbm5fUEMiLGRpbXMudXNlLCJfcmVzLiIscmVzKQpJZGVudHMoc2V1cl9jbGVhbik9ICBmYWN0b3IoSWRlbnRzKHNldXJfY2xlYW4pLGxldmVscyA9IDA6KGxlbmd0aCh1bmlxdWUoSWRlbnRzKHNldXJfY2xlYW4pKSktMSkpCkRFZ2VuZXM8LUZpbmRBbGxNYXJrZXJzKHNldXJfY2xlYW4sbWluLmNlbGxzLmdyb3VwPTIscHNldWRvY291bnQudXNlID0gMC4wMSwgbWF4LmNlbGxzLnBlci5pZGVudCA9IDEwMDAsdmVyYm9zZSA9IEYpICMjIEJ5IHVzaW5nIGEgc21hbGxlciBwc2V1ZG9jb3VudC51c2UgdGhhbiB0aGUgZGVmYXVsdCBvZiAxLCB0aGUgYXZnbG9nRkMgdmFsdWVzIGNvcnJlc3BvbmQgbW9yZSBjbG9zZWx5IHRvIHRoZSBhY3R1YWwgbG9nIGZvbGQgY2hhbmdlcywgYW5kIGxlc3Mgd2VpZ2h0IGlzIGdpdmVuIHRvIGhpZ2hlciBleHByZXNzZWQgZ2VuZXMgdmVyc3VzIGxvd2x5IGV4cHJlc3NlZCBvbmVzLgpgYGAKCiMjIyMgTmV4dCx3ZSB2aXN1YWxpc2UgdGhlIGV4cHJlc3Npb24gb2YgdGhlIHRvcCBtYXJrZXIgZ2VuZXMgZm9yIGVhY2ggY2x1c3RlciBpbiBhIGhlYXRtYXAKYGBge3J9CmZlYXR1cmVzLnBsb3Q9REVnZW5lcyU+JWZpbHRlcihhdmdfbG9nRkM+MCklPiUgZ3JvdXBfYnkoY2x1c3RlciklPiV0b3BfbihuPS0zLHd0PXBfdmFsX2FkaiklPiVwdWxsKGdlbmUpICMjIyB0b3AgMyBtYXJrZXJzCmxlbmd0aChmZWF0dXJlcy5wbG90KQpgYGAKYGBge3J9CnNldXJfY2xlYW49U2NhbGVEYXRhKHNldXJfY2xlYW4sIGZlYXR1cmVzID0gcm93bmFtZXMoc2V1cl9jbGVhbikpICMjIyBTY2FsZSB0aGUgZGF0YSBmb3IgYWxsIGdlbmVzIHRvIGJlIGFibGUgdG8gdmlzdWFsaXNlIHRoZWlyIGV4cHJlc3Npb24gd2l0aCBEb0hlYXRtYXAKYGBgCmBgYHtyICwgZmlnLmhlaWdodCA9MTIsIGZpZy53aWR0aCA9MTJ9CkRvSGVhdG1hcChzZXVyX2NsZWFuLCBmZWF0dXJlcyA9IGZlYXR1cmVzLnBsb3QsIGFzc2F5ID0gIlJOQSIsIGFuZ2xlID0gOTAsIGxhYmVsID1ULCBzaXplPTQpICsKICBzY2FsZV9maWxsX2dyYWRpZW50Mihsb3cgPSAiYmx1ZSIsIG1pZCA9ICJ3aGl0ZSIsaGlnaCA9ICJyZWQiKSsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikKYGBgCgoKCiMjIyMgQ2hlY2sgdGhlIGRpc3RyaWJ1dGlvbiBvZiBlYWNoIGNsdXN0ZXIgcGVyIHNhbXBsZQpgYGB7ciAsIGZpZy5oZWlnaHQgPSAzLCBmaWcud2lkdGggPSA4LHdhcm5pbmc9RkFMU0V9CmRhdGE8LWFzLmRhdGEuZnJhbWUodGFibGUoc2V1cl9jbGVhbiRzYW1wbGUsIElkZW50cyhzZXVyX2NsZWFuKSkpCmNvbG5hbWVzKGRhdGEpPWMoInNhbXBsZSIsImNsdXN0ZXIiLCJGcmVxIikKZ2dwbG90KGRhdGEsIGFlcyh4PWNsdXN0ZXIsIHk9RnJlcSwgZmlsbD1zYW1wbGUpKStnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikrZ2d0aXRsZShwYXN0ZTAoIk51bWJlciBvZiBjZWxscyBwZXIgY2x1c3RlciAocmVzb2x1dGlvbiA9IixyZXMsIikiKSkrdGhlbWVfY2xhc3NpYygpCmBgYAoKIyMjIyBWaXN1YWxpc2UgdGhlIGV4cHJlc3Npb24gb2YgbWFya2VyIGdlbmVzIG9uIGEgVU1BUCBwbG90CmBgYHtyICwgZmlnLmhlaWdodCA9IDE1LCBmaWcud2lkdGggPTExLHdhcm5pbmc9RkFMU0V9CkZlYXR1cmVQbG90KHNldXJfY2xlYW4sZmVhdHVyZXM9YygiQ2NyMiIsIkx5NmMyIiwiQzFxYSIsIlNwYXJjIiwiUDJyeTEyIiwiVGdmYmkiLCJNczRhNyIsIk1raTY3IiwiR2F0YTIiLCJIZGMiLCJDcGEzIiwiUzEwMGE5IiwiQ3NmM3IiLCJGbHQzIiwiWGNyMSIsIkNkMjA5YSIsIkgyLURNYjIiLCJDY3I3IiwiQ2NyOSIsIk1zNGExIiwiSmNoYWluIiwgIkNkM2UiLCJDZDhhIiwiQ2Q0IiwiRm94cDMiLCJLbHJiMWMiLCJTb3gyIiwiUGxwMSIpKQpgYGAKYGBge3IgfQpzZXVyX2NsZWFuJG5Db3VudF9STkFfbG9nPWxvZzIoc2V1cl9jbGVhbiRuQ291bnRfUk5BKQpzZXVyX2NsZWFuJG5GZWF0dXJlX1JOQV9sb2c9bG9nMihzZXVyX2NsZWFuJG5GZWF0dXJlX1JOQSkKYGBgCgojIyMjIFZpc3VhbGlzZSB0aGUgZXhwcmVzc2lvbiBvZiBtYXJrZXIgZ2VuZXMgdXNpbmcgYSBEb3RQbG90CmBgYHtyICwgZmlnLmhlaWdodCA9IDYsIGZpZy53aWR0aCA9MTMsd2FybmluZz1GQUxTRX0KRG90UGxvdChzZXVyX2NsZWFuLGZlYXR1cmVzPWMoIlB0cHJjIiwiQ2NyMiIsIkx5NmMyIiwiUGxhYzgiLCJDMXFhIiwiU3BhcmMiLCJTYWxsMSIsIlRnZmJpIiwiTXM0YTciLCJNa2k2NyIsIlN0bW4xIiwiTWNtMyIsIklmaXQzIiwiTHk2ZyIsIkNzZjNyIiwiUzEwMGE5IiwiUzEwMGE4IiwiRmx0MyIsIlhjcjEiLCJDZDIwOWEiLCJIMi1ETWIyIiwiTmFwc2EiLCJTaWdsZWNoIiwiQ2NyOSIsIkNjcjciLCJDYWNuYjMiLCJDY2wyMiIsICJDZDNlIiwiQ2Q4YSIsIkNkNCIsIkxlZjEiLCJUY2Y3IiwiRm94cDMiLCJJbDJyYSIsIlRjcmctQzEiLCJUcmRjIiwiS2xyYjFjIiwiTmNyMSIsIkdubHkiLCAiQ2QxOSIsIk1zNGExIiwiU2RjMSIsIkpjaGFpbiIsIk1zNGEyIiwgIkdhdGEyIiwiQ2QyMDByMyIsIkNkaDEiLCJIZ2YiLCJIZGMiLCJNY3B0MSIsIkNwYTMiLCJTb3gyIiwiR2pjMyIsIlBscDEiLCAiZG91YmxldC5zY29yZSIsInBlcmMubWl0byIsIm5Db3VudF9STkFfbG9nIiwibkZlYXR1cmVfUk5BX2xvZyIpKStSb3RhdGVkQXhpcygpCmBgYAoKIyMjIyBBc3NpZ24gY2VsbCB0eXBlcyB0byB0aGUgY2x1c3RlcnMsIGJhc2VkIG9uIHRoZSBtYXJrZXIgZ2VuZSBleHByZXNzaW9uCmBgYHtyLHdhcm5pbmc9RkFMU0V9CklkZW50cyhzZXVyX2NsZWFuKSA8LSBwbHlyOjptYXB2YWx1ZXMoeCA9IElkZW50cyhzZXVyX2NsZWFuKSwgZnJvbSA9IDA6MjUsIAogICAgIHRvID1jKCJUQU0gMSIsIkNEOCBUIGNlbGxzIiwiVEFNIDIiLCJUQU0gMyIsIlRBTSA0IiwiQXJ0ZWZhY3QiLCJOSyBjZWxscyIsImNEQzIiLCJCYXNvcGhpbHMiLCJUcmVnIGNlbGxzIiwiVEFNIFByb2xpZmVyYXRpbmciLCJtaWdEQyIsIk1vbm9jeXRlcyIsImNEQzEiLCJUIGNlbGxzIFByb2xpZmVyYXRpbmciLCJDRDQgVCBjZWxscyAxIiwicERDIiwiQ0Q0IFQgY2VsbHMgMiIsIlRBTSA1IiwiUGxhc21hIGNlbGxzIiwiQiBjZWxscyIsIlRBTS9UIGRvdWJsZXRzIiwiTWFzdCBjZWxscyIsIk5ldXRyb3BoaWxzIiwiT2xpZ29kZW5kcm9jeXRlcyIsIlRBTS9CYXNvcGhpbCBkb3VibGV0cyIpKQpgYGAKYGBge3IgLCBmaWcuaGVpZ2h0ID0gOCwgZmlnLndpZHRoID0gMTIsd2FybmluZz1GQUxTRX0KRGltUGxvdChzZXVyX2NsZWFuLHJlcGVsID1ULGxhYmVsPVQpIApgYGAKCmBgYHtyICwgZmlnLmhlaWdodCA9IDYsIGZpZy53aWR0aCA9MTQsd2FybmluZz1GQUxTRX0KRG90UGxvdChzZXVyX2NsZWFuLGZlYXR1cmVzPWMoIlB0cHJjIiwiQ2NyMiIsIkx5NmMyIiwiUGxhYzgiLCJDMXFhIiwiU3BhcmMiLCJTYWxsMSIsIlRnZmJpIiwiTXM0YTciLCJNa2k2NyIsIlN0bW4xIiwiTWNtMyIsIklmaXQzIiwiTHk2ZyIsIkNzZjNyIiwiUzEwMGE5IiwiUzEwMGE4IiwiRmx0MyIsIlhjcjEiLCJDZDIwOWEiLCJIMi1ETWIyIiwiTmFwc2EiLCJTaWdsZWNoIiwiQ2NyOSIsIkNjcjciLCJDYWNuYjMiLCJDY2wyMiIsICJDZDNlIiwiQ2Q4YSIsIkNkNCIsIkxlZjEiLCJUY2Y3IiwiRm94cDMiLCJJbDJyYSIsIlRjcmctQzEiLCJUcmRjIiwiS2xyYjFjIiwiTmNyMSIsIkdubHkiLCAiQ2QxOSIsIk1zNGExIiwiU2RjMSIsIkpjaGFpbiIsIk1zNGEyIiwgIkdhdGEyIiwiQ2QyMDByMyIsIkNkaDEiLCJIZ2YiLCJIZGMiLCJNY3B0MSIsIkNwYTMiLCJTb3gyIiwiR2pjMyIsIlBscDEiLCAiZG91YmxldC5zY29yZSIsInBlcmMubWl0byIsIm5Db3VudF9STkFfbG9nIiwibkZlYXR1cmVfUk5BX2xvZyIpKStSb3RhdGVkQXhpcygpCmBgYAoKIyMjICAxMCkgUkVNT1ZFIERPVUJMRVRTIEFORCBBUlRFRkFDVFMKUmVtb3ZlIHRoZSBkb3VibGV0cyBhbmQgYXJ0ZWZhY3RzICAtIGNsdXN0ZXIgd2l0aCBoaWdoIG1pdG9jaG9kcmlhbCBjb250ZW50IGFuZCB0aGUgb2xpZ29kZW5kcm9jeXRlcyBhcyB0aGUgbGF0dGVyIGFyZSBwcm9iYWJseSBkdWVzIHRvIGltcHVyaXRpZXMgZHVyaW5nIHRoZSBDRDQ1KyBzb3J0aW5nCgoKYGBge3IgLCBmaWcuaGVpZ2h0ID0gNSwgZmlnLndpZHRoID0gOCx3YXJuaW5nPUZBTFNFfQpkYXRhPC1hcy5kYXRhLmZyYW1lKHRhYmxlKHNldXJfY2xlYW4kZG91YmxldC5jbGFzcywgSWRlbnRzKHNldXJfY2xlYW4pKSkKY29sbmFtZXMoZGF0YSk9YygiZG91YmxldC5jbGFzcyIsImNsdXN0ZXIiLCJGcmVxIikKZ2dwbG90KGRhdGEsIGFlcyh4PWNsdXN0ZXIsIHk9RnJlcSwgZmlsbD1kb3VibGV0LmNsYXNzKSkrZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpK2dndGl0bGUocGFzdGUwKCJOdW1iZXIgb2YgY2VsbHMgcGVyIGNsdXN0ZXIgKHJlc29sdXRpb24gPSIscmVzLCIpIikpK3RoZW1lX2NsYXNzaWMoKStSb3RhdGVkQXhpcygpCmBgYApgYGB7cn0KY2VsbHMua2VlcD1XaGljaENlbGxzKHNldXJfY2xlYW4sIGlkZW50cyA9YygiQXJ0ZWZhY3QiLCJUQU0vVCBkb3VibGV0cyIsIlRBTS9CYXNvcGhpbCBkb3VibGV0cyIsIk9saWdvZGVuZHJvY3l0ZXMiKSwgaW52ZXJ0ID0gVCApCmNhdChuY29sKHNldXJfY2xlYW4pLWxlbmd0aChjZWxscy5rZWVwKSwiY2VsbHMgdG8gYmUgcmVtb3ZlZCIpCmBgYApSZW1vdmUgY2VsbHMgYW5kIHJlcnVuIEhWRyBkZXRlY3Rpb24sUENBIGFuZCBVTUFQCmBgYHtyfQpzZXVyX2FydGVmYWN0c19yZW1vdmVkPXNldXJfY2xlYW5bLGNlbGxzLmtlZXBdCnNldXJfYXJ0ZWZhY3RzX3JlbW92ZWQgPC0gRmluZFZhcmlhYmxlRmVhdHVyZXMoc2V1cl9hcnRlZmFjdHNfcmVtb3ZlZCx2ZXJib3NlPUYpCnNldXJfYXJ0ZWZhY3RzX3JlbW92ZWQgPC0gU2NhbGVEYXRhKHNldXJfYXJ0ZWZhY3RzX3JlbW92ZWQsdmVyYm9zZT1GKQpzZXVyX2FydGVmYWN0c19yZW1vdmVkIDwtIFJ1blBDQShzZXVyX2FydGVmYWN0c19yZW1vdmVkLCBmZWF0dXJlcyA9VmFyaWFibGVGZWF0dXJlcyhzZXVyX2FydGVmYWN0c19yZW1vdmVkKSAsdmVyYm9zZT1GKQpgYGAKCmBgYHtyICwgZmlnLmhlaWdodCA9IDQsIGZpZy53aWR0aCA9IDYsd2FybmluZz1GQUxTRX0KIyMjIyBTZWxlY3QgUENzIGZvciBkb3duc3RyZWFtIGFuYWx5c2lzIG9mIHRoZSBkYXRhc2V0CkVsYm93UGxvdChvYmplY3QgPSBzZXVyX2FydGVmYWN0c19yZW1vdmVkLG5kaW1zID01MCkKYGBgCkhlYXRtYXAgb2YgdGhlIGdlbmVzIHRoYXQgZHJpdmUgZWFjaCBQQwpgYGB7ciAsIGZpZy5oZWlnaHQgPSAxNSwgZmlnLndpZHRoID0gMTIsd2FybmluZz1GQUxTRX0KRGltSGVhdG1hcChzZXVyX2FydGVmYWN0c19yZW1vdmVkLCBkaW1zID0gMTozMCwgY2VsbHMgPSA1MDAwLCBiYWxhbmNlZCA9IFRSVUUpCmBgYAoKUnVuIFVNQVAKYGBge3IgLCBmaWcuaGVpZ2h0ID0gNCwgZmlnLndpZHRoID0gNH0KZGltcy51c2U8LTMwICMjIyBGaW5hbCBQQyBzZWxlY3Rpb24gZm9yIGRvd25zdHJlYW0gYW5hbHlzaXMKc2V1cl9hcnRlZmFjdHNfcmVtb3ZlZCA8LSBSdW5VTUFQKHNldXJfYXJ0ZWZhY3RzX3JlbW92ZWQsIGRpbXMgPSAxOmRpbXMudXNlLCB2ZXJib3NlPUYpCmBgYAoKCmBgYHtyICwgZmlnLmhlaWdodCA9IDYsIGZpZy53aWR0aCA9IDd9CkRpbVBsb3Qob2JqZWN0ID0gc2V1cl9hcnRlZmFjdHNfcmVtb3ZlZCwgZ3JvdXAuYnkgPSAic2FtcGxlIixsYWJlbD1ULCByZXBlbD1UKSsKICBnZ3RpdGxlKHBhc3RlMCgiU2FtcGxlczogVU1BUCBQQz0gIixkaW1zLnVzZSkpCmBgYAoKIyMjIyBFeHBsb3JlIERFIGdlbmVzIGJldHdlZW4gY2x1c3RlciBUQU01IGFuZCBUQU0xIHRocm91Z2ggVm9sY2FubyBwbG90CmBgYHtyICwgZmlnLmhlaWdodCA9IDMsIGZpZy53aWR0aCA9IDh9CmNvbnRyYXN0Lm5hbWU9IlRBTTV2c1RBTTEiCkRFR19UQU01dnNUQU0xPUZpbmRNYXJrZXJzKHNldXJfYXJ0ZWZhY3RzX3JlbW92ZWQsaWRlbnQuMSA9ICJUQU0gNSIsaWRlbnQuMiA9ICJUQU0gMSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHBzZXVkb2NvdW50LnVzZSA9IDAuMDEsIHZlcmJvc2UgPSBGKQpERUdfVEFNNXZzVEFNMSRnZW5lPXJvd25hbWVzKERFR19UQU01dnNUQU0xKQpgYGAKYGBge3IgLCBmaWcuaGVpZ2h0ID0gMywgZmlnLndpZHRoID0gOH0KZ2dwbG90KGRhdGE9REVHX1RBTTV2c1RBTTEsIGFlcyh4PSBhdmdfbG9nRkMsIHkgPSAtbG9nMTAocF92YWxfYWRqKSkpICsKICAgIGdlb21fcG9pbnQoYWxwaGEgPSAxLCBzaXplPTIsIAogICAgICAgICAgICAgICBhZXMoY29sID0gcF92YWxfYWRqPDFlLTIwICYgKGF2Z19sb2dGQz4xIHwgYXZnX2xvZ0ZDPCAtMSkpKSArCiAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiZGFyayBncmV5IiwgImJyb3duMSIpKSArCiAgICBnZW9tX3RleHRfcmVwZWwoZGF0YT1zdWJzZXQoREVHX1RBTTV2c1RBTTEsICAKICAgICAgICAgICAgICAgICAgICBwX3ZhbF9hZGo8MWUtMTAwICYgKGF2Z19sb2dGQz4xIHwgYXZnX2xvZ0ZDPCAtMSkpLGFlcyhsYWJlbCA9IGdlbmUpLCBzaXplID00KSsKICAgIHRoZW1lX2NsYXNzaWMoKSt0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpK2dndGl0bGUoY29udHJhc3QubmFtZSkKYGBgCgojIyMgR2VuZSBPbnRvbG9neSBlbnJpY2htZW50IHdpdGggTWV0YXNjYXBlIG9mIHRoZSBnZW5lcyB1cHJlZ3VsYXRlZCBpbiBjbHVzdGVyIFRBTTUgY29tcGFyZWQgdG8gY2x1c3RlciBUQU0xClNlbGVjdCB0b3AgdXByZWd1bGF0ZWQgREVHIChhcyB0aG9zZSBnZW5lcyBhcmUgc3BlY2lmaWMgZm9yIHRoZSBjbHVzdGVyIG9mIGludGVyZXN0KS4gSXQgaXMgYmVzdCB0byBoYXZlIGF0IGxlYXN0IDIwLTUwIGdlbmVzIGZvciBHTyBlbnJpY2htZW50LgpgYGB7cn0KcF90cmVzaD0xZS0yMApsb2dGQ190aHJlc2g9MQpzZWxlY3RlZC5nZW5lcz1ERUdfVEFNNXZzVEFNMVtERUdfVEFNNXZzVEFNMSRwX3ZhbF9hZGo8cF90cmVzaCAmIERFR19UQU01dnNUQU0xJGF2Z19sb2dGQz5sb2dGQ190aHJlc2gsImdlbmUiXQpsZW5ndGgoc2VsZWN0ZWQuZ2VuZXMpCmBgYApFeHBvcnQgZ2VuZSBuYW1lcyBhcyBhIGNzdiBmaWxlIGZvciBtZXRhc2NhcGUKYGBge3J9CndyaXRlLnRhYmxlKHNlbGVjdGVkLmdlbmVzLCBmaWxlPXBhc3RlMCgiVXByZWd1bGF0ZWRfZ2VuZXNfIixjb250cmFzdC5uYW1lLCJfcF92YWxfYWRqLiIscF90cmVzaCwiX2xvZ0ZDLiIsbG9nRkNfdGhyZXNoLCIuY3N2IiksIHJvdy5uYW1lcyA9IEYsIGNvbC5uYW1lcyA9IEYsIHNlcD0iLCIpCmBgYAojIyMjIyMgUnVuIEdPIGVucmljaG1lbnQgaW4gTWV0YXNjYXBlIChodHRwczovL21ldGFzY2FwZS5vcmcvKToKIyMjIyMjIElucHV0IHRoZSAuY3N2IGZpbGUgYW5kIGRvIEV4cHJlc3MgYW5hbHlzaXMgd2l0aCAiSW5wdXQgYXMiPSBNLm11c2N1bHVzIGFuZCAiYW5hbHlzaXMgYXMiPSBNLm11c2N1bHVzID0+IEV4cHJlc3MgQW5hbHlzaXMuIFNhdmUgdGhlICJHZW5lIExpc3QgUmVwb3J0IEV4Y2VsIFNoZWV0cyIKIyMjIyMgIFZpc3VhbGl6YXRpb24gb2YgdGhlIGVucmljaGVkIHBhdGh3YXlzIG9uIHRoZSBWb2xjYW5vIHBsb3Q6CgpgYGB7cn0KR08ubWV0YXNjYXBlPXhsc3g6OnJlYWQueGxzeCggcGFzdGUwKCJNZXRhc2NhcGVfVXByZWd1bGF0ZWRfZ2VuZXNfIixjb250cmFzdC5uYW1lLCJfcF92YWxfYWRqLiIscF90cmVzaCwiX2xvZ0ZDLiIsbG9nRkNfdGhyZXNoLCIueGxzeCIpLDIgKQojU2VsZWN0IG9ubHkgdGhlIG1haW4gKHN1bW1hcnkpIHBhdGh3YXlzCkdPLm1ldGFzY2FwZSRncm91cD1zYXBwbHkoc3Ryc3BsaXQoR08ubWV0YXNjYXBlJEdyb3VwSUQsICJfIiksICJbWyIsMikKYGBgCgojIyMgVG9wIEdPIGVucmljaGVkIHBhdGh3YXk6CmBgYHtyICwgZmlnLmhlaWdodCA9NCwgZmlnLndpZHRoID0gMTAgLHdhcm5pbmc9RkFMU0V9CmluZGV4PTEKZ3NldC5zeW1ib2xzPUdPLm1ldGFzY2FwZVtpbmRleCwiU3ltYm9scyJdCmdzZXQuc3ltYm9scz1zdHJzcGxpdChnc2V0LnN5bWJvbHMsICIsIilbWzFdXQpHT3Rlcm09R08ubWV0YXNjYXBlW2luZGV4LCJEZXNjcmlwdGlvbiJdCmdncGxvdChkYXRhPURFR19UQU01dnNUQU0xLCBhZXMoeD0gYXZnX2xvZ0ZDLCB5ID0gLWxvZzEwKHBfdmFsX2FkaikpKSArCiAgICBnZW9tX3BvaW50KGFscGhhID0gMSwgc2l6ZT0yLAogICAgICAgICAgICAgICBhZXMoY29sb3I9Z2VuZSAlaW4lIGdzZXQuc3ltYm9scykpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJkYXJrIGdyZXkiLCAiYnJvd24xIikpICsKICAgIGdlb21fdGV4dF9yZXBlbChkYXRhPXN1YnNldChERUdfVEFNNXZzVEFNMSwgIAogICAgICAgICAgICAgICAgICAgZ2VuZSAlaW4lIGdzZXQuc3ltYm9scyksYWVzKGxhYmVsID0gZ2VuZSksIHNpemUgPTQpKwogICAgdGhlbWVfY2xhc3NpYygpK3RoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikrZ2d0aXRsZShwYXN0ZTAoIGNvbnRyYXN0Lm5hbWUsICI6ICIsIEdPdGVybSwgIiBwYXRod2F5IikpCmBgYAoKIyMjICJyZXNwb25zZSB0byBoeXBveGlhIiBwYXRod2F5OgpgYGB7ciAsIGZpZy5oZWlnaHQgPTQsIGZpZy53aWR0aCA9IDEwICx3YXJuaW5nPUZBTFNFfQpHT3Rlcm09InJlc3BvbnNlIHRvIGh5cG94aWEiCmdzZXQuc3ltYm9scz1HTy5tZXRhc2NhcGVbR08ubWV0YXNjYXBlJERlc2NyaXB0aW9uPT1HT3Rlcm0sIlN5bWJvbHMiXQpnc2V0LnN5bWJvbHM9c3Ryc3BsaXQoZ3NldC5zeW1ib2xzLCAiLCIpW1sxXV0KZ2dwbG90KGRhdGE9REVHX1RBTTV2c1RBTTEsIGFlcyh4PSBhdmdfbG9nRkMsIHkgPSAtbG9nMTAocF92YWxfYWRqKSkpICsKICAgIGdlb21fcG9pbnQoYWxwaGEgPSAxLCBzaXplPTIsCiAgICAgICAgICAgICAgIGFlcyhjb2xvcj1nZW5lICVpbiUgZ3NldC5zeW1ib2xzKSkgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoImRhcmsgZ3JleSIsICJicm93bjEiKSkgKwogICAgZ2VvbV90ZXh0X3JlcGVsKGRhdGE9c3Vic2V0KERFR19UQU01dnNUQU0xLCAgCiAgICAgICAgICAgICAgICAgICBnZW5lICVpbiUgZ3NldC5zeW1ib2xzKSxhZXMobGFiZWwgPSBnZW5lKSwgc2l6ZSA9NCkrCiAgICB0aGVtZV9jbGFzc2ljKCkrdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKStnZ3RpdGxlKHBhc3RlMCggY29udHJhc3QubmFtZSwgIjogIiwgR090ZXJtLCAiIHBhdGh3YXkiKSkKYGBgCgoKYGBge3IgLCBmaWcuaGVpZ2h0ID0gNiwgZmlnLndpZHRoID0gMTB9CkRpbVBsb3Qob2JqZWN0ID0gc2V1cl9hcnRlZmFjdHNfcmVtb3ZlZCxsYWJlbD1ULCByZXBlbD1UKSsKICBnZ3RpdGxlKHBhc3RlMCgiQ2VsbCBhbm5vdGF0aW9uOiBVTUFQIFBDPSAiLGRpbXMudXNlKSkKYGBgCgojIyMjIyBTYXZlIG9iamVjdApgYGB7cn0Kc2F2ZVJEUyhzZXVyX2FydGVmYWN0c19yZW1vdmVkLCBmaWxlPSJDaXRlc2VxX21vdXNlX0dCTS5zZXVyYXRPYmoucmRzIikKYGBgCgpgYGB7cix3YXJuaW5nPUZBTFNFfQpzZXNzaW9uSW5mbygpCmBgYAoKCg==